知识库的时效性问题:为什么「定时全量」不够用
业务对新鲜度的真实要求
大多数 RAG 系统的索引链路是批处理思维:夜间跑一次同步,或者每小时增量爬一轮。当知识源是文档库、产品手册这类低频更新的内容时,这套做法没有问题。但另一类知识库不是这样:工单系统、库存与价格、舆情流、交易规则、运维状态库——这些知识源的变更频率以分钟甚至秒计,而用户对答案新鲜度的要求同样严苛。
三个真实场景说明新鲜度为什么是硬需求:客服机器人回答「我的退款到账了吗」,它读取的工单状态如果是两小时前的,答非所问;运维助手被告警淹没时,如果知识库里的「当前生效的处置预案」是上一版本的,它给出的操作可能是已被回滚的;电商比价场景里,价格知识的分钟级滞后直接等于错误答案。
批处理链路在这类场景的失败不是「慢一点」的问题,而是错误的架构语义:它假设知识是静态的快照,而这类业务的知识是持续变化的状态流。把状态流的问题交给快照架构,怎么调参都是错的。
新鲜度 SLO 的定义
做流式索引,第一步是把「新鲜度」从模糊的感觉变成可度量的 SLO。我们采用的定义:从知识源发生变更(源库 commit)到该变更可被检索(索引可见)的时间差,称为索引时延;SLO 表述为「P99 索引时延 ≤ N 分钟,且不丢变更」。分两个口径:常规变更(内容更新)与关键变更(删除、权限变更),后者的 SLO 更严——删除的内容多在线一分钟就多一分钟合规风险,权限收紧的文档晚一分钟生效就是越权检索事故。
有了 SLO,架构选型有了标尺:P99 五分钟的 SLO,批处理(小时级)差两个数量级,微批流式(分钟级)刚好够;P99 秒级则要考虑双写与内存热层。先定 SLO 再选架构,而不是反过来——这句话在这里不是口号,是省钱的实用主义。
从批处理到流式索引的架构演进
Lambda 与 Kappa
把数据工程的架构遗产搬过来直接可用。Lambda 架构(Marz,2011)用「批处理层保全量 + 速度层保实时」双轨并行,优点是批层兜底、流层挂了数据不丢,缺点是同一逻辑两套实现——嵌入与切块逻辑要在批管道和流管道里各写一遍并保持行为一致,这是持续的维护税。Kappa 架构(Kreps,2014,Questioning the Lambda Architecture)砍掉批层,一切皆流:历史数据是流的重放,全量重建就是从头消费一遍事件流。对 RAG 索引场景,我们的实践结论是 Kappa 为主、批任务为辅:日常增量走事件流(唯一实现,无逻辑分裂),每月一次的低峰全量重建也复用同一条流式处理代码(从零 offset 重放),只在需要复杂历史分析时另起临时批作业。
Kappa 化的前提是事件流的永久保留(Kafka 的无限 retention 或镜像到对象存储),否则「重放即重建」不成立。流保留成本与全量重建频率之间的权衡,是容量规划的一部分。
flowchart LR
DB["业务库"] -- "CDC 变更事件" --> Q["事件队列 Kafka"]
Q --> C["消费者:切块 + 微批嵌入"]
C --> V["向量库 upsert"]
C --> H["热点缓存 Redis"]
V --> S["检索服务"]
H -. "双路查询" .-> S
Q --> A["全量重建:流重放"]
CDC:把数据库变更变成事件流
变更捕获(CDC)是流式索引的入口。逻辑复制类的 CDC 工具(Debezium 为代表)从数据库的变更日志(如 PostgreSQL 的 WAL、MySQL 的 binlog)解析出结构化的变更事件(操作类型、主键、前后镜像),投递到消息队列。相比轮询(定时扫 updated_at),CDC 的优势是不漏、不重、低延迟:变更日志是数据库权威事实,轮询脚本漏掉的边界 commit(大事务、时钟偏移)CDC 天然覆盖。
文档类知识源(对象存储里的文件、wiki 页面)没有 binlog,CDC 得自己搭:文件哈希与 mtime 的变更检测服务(轮询但轻量)产出事件进同一个队列。入口不同,事件 schema 必须统一:{source, entity_id, op, version, payload_ref, occurred_at},下游消费者只认 schema 不认来源。这一层统一是流式架构里最值得提前花时间的地方——入口 schema 乱了,下游全乱。
流式索引的工程细节
微批嵌入:吞吐与延迟的折中
嵌入 API 的调用成本结构决定了「逐条消费」不可行(请求开销与限流),「攒大批」又拉高时延。微批(micro-batching)是折中:消费者把到达的事件按 200-500ms 或 50 条一批聚合,一次调用批量嵌入。批窗口大小直接标定索引时延的下限,SLO 越紧窗口越小,但窗口小于 100ms 后吞吐急剧下降。我们生产配 300ms 窗口,配向量库的批量写入,常规变更的端到端时延稳定在两三秒,P99 远低于五分钟 SLO。
微批内部的排序要点:同 key 事件合并。一条文档一分钟内被改五次,队列里有五个版本的事件,只需对最新版本嵌入一次——按 entity_id 取 occurred_at 最新的合并后再嵌入,嵌入成本直接省四倍。这个合并还有一个正确性红利:避免乱序写入导致的旧版本覆盖新版本。
Upsert、版本与删除的涟漪
流式索引的写路径是 upsert(按 chunk_id 幂等覆盖),有三个细节决定正确性:
版本号随行。每个 chunk 记录产生它的事件 version,写入时向量库按版本做条件更新(新版本才覆盖),乱序到达的旧版本事件被静默丢弃。没有版本号,Kafka 的分区乱序(同 key 多分区、重试重投)会随机覆盖,这是流式索引最阴的 bug——不报错、偶发、难复现。
删除是涟漪,不是一行。删除源文档要级联删掉它派生的所有 chunk。如果 chunk 没有反向索引(doc_id → chunk_ids),就得靠 chunk 元数据里的 doc_id 做按过滤删除——多数向量库支持按 metadata 过滤删除,但性能差异巨大;高频删除的知识源值得维护一张 doc-chunk 映射表。删除还牵连派生数据:摘要、实体抽取产物、缓存里的答案快照,都要进同一删除事务或补偿队列。
重排与重建的成本意识。流式 upsert 在 HNSW 类索引上会产生碎片(大量删除留下死节点),定期触发索引压实(compaction)是容量运维的常规项;全天高频 upsert 的索引,重建周期要比静态索引更短。
幂等消费与乱序处理
消费者幂等靠「版本号条件写」兜底,但还有一层:重复消费的代价控制。嵌入是最贵的操作,消息队列的 at-least-once 投递意味着重复消息会重复嵌入。在嵌入前加一级去重缓存(entity_id+version 的布隆过滤器或短 TTL 缓存),把重复事件的嵌入调用省掉。乱序处理则统一收敛到版本比较:任何处理动作前先比较事件的 version 与当前已处理版本,旧的直接跳过。两条规则都很朴素,缺了哪条线上都会出「文档改回来了但搜索结果还是旧的」这类灵异故障。
读写一致性:写完立刻搜得到吗
读己之写
流式索引的端到端时延哪怕只有两三秒,仍然存在读写一致性窗口:用户刚提交的文档立刻搜索,搜不到。多数场景这是可接受的最终一致性;但「提交后立刻验证」类操作(上传文档后马上测试检索)会变成高频客诉。解法是读己之写(read-your-writes):写接口在接受写入时把该文档的 id 记入一个短 TTL 的「待可见集合」(Redis,TTL 覆盖最大索引时延),检索层查询时如果命中待可见集合且向量库还没有该文档的结果,返回明确的「已受理、索引中(预计 N 秒)」而不是静默的空结果。把不可见变成可解释,客诉消失。
热点数据的双路检索
状态类知识(工单状态、实时价格)的新鲜度要求超出了流式索引的极限(秒级也不够时),用双路检索兜底:向量库回答知识性问题,业务库或缓存直接回答状态性问题——检索层先做意图分流(该查询是查知识还是查状态),状态类查询直接走业务 API 而不是知识库。这条边界划清楚,「实时」的需求大部分根本不该进 RAG 链路:RAG 负责稳定知识,业务系统负责活状态,让每层做自己的事,比把 RAG 硬拗成实时系统便宜且可靠得多。
容量、背压与降级
流式系统的运维意识与批处理不同,三个必须预置的机制:背压——嵌入 API 限流或向量库写入拥塞时,消费者自动扩大微批窗口、放缓消费速率,宁可时延超标也不丢事件(Kafka 的 lag 是背压的压力表,监控它);降级——队列积压超过水位线时自动切换降级模式(暂停非关键源的索引、只保留删除与权限事件的即时处理),保住合规底线、牺牲新鲜度;重放演练——每季度做一次「从零 offset 全量重放」的演练,验证重建路径的可用性,真出事时重建不是一份从没跑过的文档。
监控的三条核心曲线:队列 lag(积压趋势,提前扩容的信号)、端到端索引时延分布(对齐 SLO 的 P99)、嵌入调用成功率与限流率(上游健康度)。三条曲线齐了,流式索引才算可运维。
小结
当知识的变更频率与用户的新鲜度预期同时升高,RAG 索引链路要从批处理思维切换到流式思维。架构上以 Kappa 为主:CDC(binlog 逻辑复制 + 文件哈希检测)产出统一 schema 的事件流,消费者做微批嵌入与版本化 upsert,全量重建即流重放。正确性的三根支柱:同 key 合并与版本号条件写(治乱序与重复)、删除的级联涟漪(chunk 映射 + 派生数据)、消费幂等(去重缓存 + 版本比较)。
一致性上分两层处理:读写窗口用「待可见集合」把静默不可见变成可解释的受理反馈;超出索引链路极限的状态类查询走意图分流,让业务系统回答活状态,RAG 只回答稳定知识。运维上背压、降级、重放演练三机制预置,lag、端到端时延、上游健康三条曲线盯盘。流式索引不是给 RAG 堆新技术,而是把它从「定期拍照」改造成「持续响应世界的变化」——SLO 先行,架构跟着 SLO 走。
读者留言
COMMENTS 暂无还没有留言,来说第一句?