知识库的重灌与切换
知识库不是建完就固定的东西。文档会更新、会下架、会改权限,而索引里的内容不会自己跟着变。
这件事的难点不在"重灌"本身,而在于重灌期间系统要继续对外服务。直接改现有的索引,会让查询落在一个新旧混合的状态上——一半是旧向量一半是新向量,检索结果毫无规律,而且这种问题不会报错,只会表现为"最近答案质量有点飘"。
1. 先分清三种变更
不同变更的处理成本差很多,不该一律走全量重灌。
文档内容更新 —— 只影响这份文档的切片。重算这些切片、替换对应向量即可。
切片策略或 Embedding 模型变更 —— 影响全库,必须全量重灌。这两件事都应该被视为"迁移",而不是日常操作。
元数据变更(权限、版本、语种标签) —— 最轻,通常不需要重算向量,只需要更新对应的列。但它最容易被忽略:一份文档权限改了,向量还在,检索还能召回——这时候出现的不是质量问题,是越权。
2. 增量同步怎么做
2.1 切片要有稳定 ID
这是增量更新的前提。如果切片 ID 是数组下标或者随机生成的,文档改一个字之后所有切片 ID 全变,增量就无从谈起——只能全量重算。
稳的做法是用路径定位 + 内容寻址的组合:
chunkId = hash(文档路径 + 章节标题路径 + 切片规范化文本)
这样同一段内容在两次处理之间 ID 稳定,变化的部分能被精确定位。
最后一项必须是内容本身,不能用序号。用序号做 ID 时,在文档开头插入一段会让后面所有切片的序号整体后移,ID 跟着全体改变,增量更新就退化成全量重算。用规范化后的切片文本参与哈希,插入只影响被改动的那一片,其余切片的 ID 不动。
2.2 变更检测
三种常见来源,可靠性依次上升:
- 时间戳(updated_at)—— 最简单,但不可靠:源文件系统的时间戳可能被改写,也可能精度不够。
- 内容哈希 —— 对每个切片算哈希,比对是否变化。可靠,代价是要读一遍内容。
- 变更事件 —— 上游系统发出变更通知(CDC、webhook)。最及时,也最准确,但要求上游配合。
生产上通常是"事件为主 + 定期全量校验兜底":事件驱动做日常同步,离线任务周期性核对全库哈希,兜住漏掉的事件。
2.3 删除比更新更容易出错
文档下架时,删向量、删切片、删元数据三件事要一起做。只删元数据不删向量,会出现"搜得到但读不到内容"的悬空结果。
推荐做法是软删除 + 过期清理:先标记为已删除并立即从检索结果中排除,再由清理任务真正删除。这样删除操作是幂等的,也给误删留了回滚窗口。
另外一层是向量索引自己的问题:pgvector 的 HNSW 删除是标记删除——被删的元组会一直留到 vacuum 或 reindex,期间可能少返回结果、索引体积也不回收,而重建之前没有任何报错。更隐蔽的是图结构本身:hnswlib 一类的实现在大量删除后,图上会留下不连通的边,表现为召回悄悄下降(pgvector 官方没有明文记载这个退化机制,但"删得多会掉召回"在多个项目的讨论里都能看到)。这也是下一节坚持"建新版本再切指针"、而不是原地增删的原因之一。
3. 版本化:查询侧永远指向一个完整版本
重灌期的正确做法是并行建新版本,切换时改指针,而不是原地更新。
具体流程:
- 用新的配置(或新模型)在后台构建 v2 索引
- v2 就绪后做一次校验:抽样查询对比、召回指标、切片总数是否合理
- 原子切换查询侧的指针,从 v1 到 v2
- 观察一段时间,确认无异常后释放 v1
好处是两条:切换是原子的,不存在中间状态;回滚同样是指针操作,不需要重建。
元数据层面也要跟着版本化:每条切片带上"来自哪个版本的索引"和"源文档版本"。这样排查问题时能回答"这条结果是用哪套配置生成的"。
4. 多语言知识库多一层
多语言场景(比如同一个产品文档有九种语言版本)有个额外问题:各语言的更新不同步。
英文文档更新了,中文还是旧的,用户用中文问得到旧答案,用英文问得到新答案。这不是检索 bug,但用户感知就是"系统时灵时不灵"。
处理办法:
按语种独立版本化。 每个语种一套版本标记,不要混成一个"文档版本"。
允许查询时声明语种偏好。 优先返回该语种的切片,缺了再考虑跨语言检索——是先把问题翻译成文档语言再检索,还是先检索再翻译结果,这两种做法各有取舍,要按语料情况定死一种,别混用(三种路径的详细对比见《跨语言检索的三条路》)。
把"未同步"显式暴露出来。 在运营侧给出各语种的版本差异视图,让人知道哪些语言落后了。这件事技术上不难,难的是有人去做。
5. 一致性检查清单
上线前跑一遍这几项:
- 切片总数是否和预期相符(突然少一半,多半是解析失败了)
- 是否存在悬空切片(有向量但元数据里查不到源文档)
- 权限标签是否覆盖全部切片(任何一个漏标的切片都是越权风险)
- 抽样 20 个真实问题,看召回的切片版本是否正确
- 删除操作是否幂等(重复执行不会出错)
最后一项在自动化上最常被跳过,而它决定了出问题时能不能安全重试。这个思路和《RAG 链路与失败模式》里那条"每一段都要能单独看中间结果"是一致的——索引状态看不见,出了问题就只能全量重建。
6. 一个判断标准
问:如果现在发现三周前的切片策略有问题,能不能在不影响线上服务的前提下重灌并切换?
能,说明版本化做对了。不能(需要停机、或者必须原地改),说明这套索引还停留在"一次性产物"的阶段。