知识库怎么增量更新:全量重建撑不住

2026-07-28

数据截至 2026-07,规范与各项目能力以官方文档当前版本为准。

知识库的增量更新不是一个”性能优化”问题,而是一个数据一致性问题:只要你的语料会被人改、会被删、会被重新命名,全量重建迟早会在时间窗口、成本和”旧答案还在”这三件事上同时失守。真正要设计的东西只有一件——让系统知道”哪些东西变了”,并且能把这个变化准确地传导到向量库、图谱和缓存的每一层。

一个很常见的误解是:既然重建一次也就跑几十分钟,那干脆每天夜里全量重跑一遍,逻辑最简单、也最不容易出错。这个判断在语料只有几百份文档、且没人半夜改东西的时候确实成立。但它有一个隐含前提——重建的时间和成本相对语料规模是线性且可接受的。一旦语料涨到几万份、里面还混着扫描版 PDF 和需要版面解析的报表,重建就从”跑一晚上”变成”跑不完”,而解析和嵌入这两段恰恰是整条链路里最烧钱的部分。更麻烦的是,全量重建期间知识库处于什么状态?如果是原地覆盖,用户在这段时间内检索到的是半新半旧的混合体;如果是双库切换,你的存储成本直接翻倍。

一、先看清楚全量重建贵在哪

把重建拆开看,成本集中在三段:

  • 解析:把 PDF、Word、表格、扫描件转成结构化文本。版式复杂的文档尤其吃资源,有的引擎(比如 RAGFlow 这类强调深度文档理解、做版面感知解析的开源方案)会额外跑版面识别,单份文档的耗时远高于纯文本。这一段的开销跟文档数量成正比,跟”你改没改它”完全无关。
  • 嵌入:每个分块调一次嵌入模型。用云端 API 就是真金白银按 token 计费,用本地模型就是 GPU 时间。全量重建意味着 绝大部分调用是在为没有变化的内容重复付费。
  • 写入与索引重建:向量库要重新建索引,期间检索性能会受影响,量化、payload 索引这些结构也要跟着重算。

三段里前两段是纯浪费。真实业务中一天的文档变更量通常只占语料的很小一部分,把整库重跑一遍,本质上是用计算成本换实现上的省事。语料小的时候这笔交易划算,大了之后就不划算了。

二、变更从哪来:三种感知方式

要做增量,第一步得知道”什么变了”。按可靠性从高到低,常见的有下面三种:

事件驱动。上游系统(内容管理系统、工单系统、Git 仓库)在文档创建、修改、删除时主动发一条消息,你的 ingestion 管道订阅这个消息。这是最干净的方式,延迟低、不遗漏,缺点是需要上游配合改造,很多时候你根本没有这个话语权。

轮询扫描 + 元数据比对。定时遍历数据源,比对文件的修改时间、大小、版本号。实现简单、不依赖上游,问题是元数据不可靠——有些系统会因为权限变更、批量迁移之类的操作把整批文件的修改时间刷新一遍,你会看到”一夜之间所有文档都变了”;反过来,也有系统在内容改动后不更新修改时间,导致漏检。所以修改时间只能当作”疑似变更”的筛选条件,不能当作最终判据。

全量拉取 + 内容比对。每次都把内容读出来,靠内容本身的哈希判断有没有变。这个方式最准,但它省掉的只是解析之后的嵌入成本,读取和解析仍然要跑。对于解析很贵的语料,收益有限。

实践中比较稳的组合是:能拿到事件就用事件,拿不到就用轮询筛出候选集,再用内容哈希做最终确认。轮询负责”缩小范围”,哈希负责”不出错”。

三、内容指纹:增量更新的地基

不管用哪种感知方式,都需要一个东西来回答”这份内容跟我库里存的是不是同一份”。这就是内容指纹——通常是对规范化之后的文本内容做哈希。

有两个细节容易被忽略。

第一,指纹要建在解析之后、分块之前的那段纯文本上。 如果直接对原始文件字节做哈希,同一份文档换个导出工具、多一个空白页、元数据里塞了个时间戳,哈希就变了,但正文一个字没改,你会白白重跑一遍。反过来,如果对分块之后的结果做哈希,那分块参数一变全库指纹全变,也失去意义。取中间那一层最稳。

第二,规范化的规则要固定下来并且写进文档。 空白字符怎么归一、换行怎么处理、页眉页脚要不要剥掉——这些规则一旦调整,全库指纹会集体失效,等于触发一次隐性的全量重建。所以规范化逻辑应该带一个版本号,跟指纹一起存,改动时你能明确知道影响面有多大。

指纹存在哪里?建议单独维护一张文档级的状态表(关系型数据库就够),记录文档 ID、来源路径、内容指纹、分块策略版本、嵌入模型标识、最后同步时间、当前状态。向量库里存的是向量和 payload,它不适合承担”同步状态机”这个职责。把状态表和向量库分开,出问题时你至少还有一份可信的账本能对。

四、删改怎么落到向量库

判断出某份文档变了之后,接下来是最容易出错的一步:怎么把这个变化落到向量库里。

一份文档通常对应多个分块,也就是多条向量记录。文档改了之后,新的分块数量、边界、内容都可能跟旧的对不上——你不能指望”第 3 块换成新的第 3 块”这种一一对应。可靠的做法是按文档 ID 整体替换:把该文档名下所有旧分块删掉,再把新分块整批写入。这要求向量库支持按 payload 字段做条件删除,常见方案多有提供,以各产品文档为准,比如 Qdrant 就支持过滤和 payload 索引,做这类按元数据批量操作时不至于全表扫。

有人会想更精细一点:只重嵌入真正变化的分块。这在技术上做得到,思路是对每个分块单独算指纹,新旧两组分块做集合比对,交集部分复用旧向量。文档很长、每次只改一小段时,这个优化能省不少嵌入调用。但代价是逻辑复杂度上一个台阶,而且只要分块边界发生位移(在文档开头插入一段话,后面所有分块的切分点都会挪),交集就会大面积落空,优化直接失效。除非你的文档确实很长、修改确实很局部,否则先做文档级整体替换,简单可靠。

删除的情况更要小心。源文档被删了,向量库里的分块必须跟着删,否则用户会检索到一份已经不存在的文件里的内容,还煞有介事地给出引用链接。这类”幽灵答案”在合规场景里是硬伤。轮询式感知天然发现不了删除——文件不在了,你的扫描结果里也就没有它。所以状态表要做一次反向对账:本轮扫描到的文档 ID 集合,跟状态表里标记为”活跃”的集合做差集,差集里的就是被删掉的。

五、软删除与版本号

直接把向量记录删掉,操作是不可逆的。上游误删了一批文档、或者同步任务因为权限问题拉了个空列表,一次对账就能把知识库清空一大半,恢复只能靠全量重建。

更稳妥的做法是软删除加版本号:向量记录的 payload 里带一个状态字段和一个版本号,检索时用过滤条件只召回”活跃”状态的记录。删除操作只改状态位,物理清理交给一个延迟一段时间的后台任务。这样误操作有回滚窗口,代价是存储会有一定冗余,以及每次检索都多一个过滤条件——这也是为什么 payload 索引值得提前建好,否则过滤本身会成为新的性能瓶颈。

版本号还有一个用处:支持”原子切换”。写入新版本分块时先不激活,全部写完再把该文档的活跃版本号从旧值改成新值。这样更新过程中用户看到的要么是完整的旧版,要么是完整的新版,不会撞上半新半旧的中间态。对于一次更新涉及很多分块的长文档,这个保证挺重要。

六、分块策略一改,增量就失效

这是最容易被低估的一条:增量更新的前提是分块策略和嵌入模型都没变。

分块大小、重叠长度、切分规则任意一项调整,旧向量和新向量就不在同一套语义划分下了,混在一个库里检索结果会变得难以解释。同样,换了嵌入模型——哪怕只是同一家的新版本——向量空间完全不同,新旧向量之间的距离计算毫无意义。

所以状态表里那两个字段(分块策略版本、嵌入模型标识)不是摆设,它们是判断”这次能不能走增量”的开关。策略变了就得老老实实全量重建,区别只是你知道自己在重建,而不是稀里糊涂地把两套体系混在一起。

这也反过来说明一件事:分块策略和嵌入模型应该在上量之前尽量定下来。调优期在小规模样本上折腾,别在几万份文档的库上反复换参数。检索段的调优思路可以参考 RAG 检索调优:嵌入、混合检索与重排的取舍,先在评估集上把方向确定,再往全量语料上推。

七、派生物怎么跟着更新

向量只是知识库的一层。如果你还做了别的加工,每一层都要有自己的更新路径:

  • 知识图谱。图检索类方案(RAGFlow 内置的知识图谱构建就属于这一类)会从文档里抽实体和关系。文档一改,抽出来的实体可能消失、关系可能反转。图谱的增量更新比向量难得多,因为关系是跨文档的——删掉一份文档,某个实体可能就此失去所有支撑边,但它在别的文档里可能还有引用。务实的做法是给图谱设一个独立的、频率更低的重建节奏,别强行跟向量同步。
  • 文档摘要 / 父子索引。如果你为每份文档生成了摘要用于粗筛,摘要要跟着正文一起重新生成,否则会出现”摘要说有、正文里没有”的错位。
  • 检索结果缓存。缓存是最容易被忘掉的一层。文档更新后不清缓存,用户拿到的还是旧答案,而且这种问题特别难排查——你查数据库是新的,查向量库也是新的,就是回答不对。稳妥做法是让缓存 key 带上涉及文档的版本号,版本一变缓存自然失效。

八、一个最小可落地的方案

不依赖特定框架,用五个步骤就能搭起来:

  1. 建状态表:文档 ID、来源标识、内容指纹、分块策略版本、嵌入模型标识、活跃版本号、状态、更新时间。
  2. 跑同步任务:拉取当前数据源的文档清单,逐份解析成纯文本,算指纹,跟状态表比对,分出新增、变更、未变三类。
  3. 反向对账找删除:本轮清单与状态表活跃集合做差集,差集标记为待删除。
  4. 落库:新增和变更的文档走”分块 → 嵌入 → 写入新版本 → 切换活跃版本 → 软删除旧版本”;待删除的文档直接改状态位。
  5. 清理与校验:延迟一段时间后物理清理软删除记录;每次任务结束输出一份对账报告,包含各类文档的数量、失败清单、状态表与向量库的记录数差异。

第五步的对账报告别省。增量更新出问题通常是静默的——不报错,只是某几份文档悄悄没同步上,等到用户投诉”我明明改了怎么还是老答案”,你已经不知道是哪一天开始漏的了。有一份每次都产出的数量对账,异常会自己浮上来。

九、诚实说说局限

增量更新解决的是”同步成本”和”数据一致性”,它解决不了下面这些:

  • 它不会让检索效果变好。分块烂、嵌入不合适、缺重排,增量做得再精细也是把烂结果同步得更及时。效果问题要靠评估驱动,思路可以看 RAG 怎么评估?凭感觉调是调不出来的
  • 它救不了解析这一段。如果你的语料是扫描件和复杂版式,解析仍然是每份变更文档都要重跑的固定成本,增量只是让你不用为没变的文档重跑而已。解析本身的取舍见 RAG 效果差?先确认瓶颈是不是在解析这一步
  • 它增加了系统的状态。状态表本身会不一致、会脏、会跟向量库对不上。多了一套状态就多了一处要维护的东西,语料规模不大的时候,每天全量重跑反而是更理性的选择。别为了”看起来更工程化”提前上复杂度。
  • 框架自带的能力参差不齐。有的框架提供了文档存储和 ingestion 管道的抽象,能省掉一部分手写逻辑;有的只给你一个写入接口,状态管理全靠自己。选型时值得把这一点算进去,具体各项目支持到什么程度,以官方文档当前版本为准。框架层面的取舍可以参考 RAG 框架怎么选?按瓶颈选,别按 star 数选

小结

全量重建不是错的做法,它只是有适用边界——语料小、变更不频繁、重建窗口够用的时候,它是最省心的选择。真正需要切到增量的信号是:重建时间开始逼近可用窗口,或者嵌入成本里有很大比例花在没变的内容上。切换时最该先做对的是内容指纹和文档级的状态表,这两样是地基,比”只重嵌入变化的分块”这类精细优化重要得多。删除路径和缓存失效是两个最常被漏掉的环节,前者制造幽灵答案,后者制造查不出原因的旧答案。最后记住一条硬约束:分块策略或嵌入模型一变,增量就不成立,那时候要做的是主动全量重建,而不是让两套向量混在一个库里。

接下来看什么

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。