RAG 中如何实现向量数据库的增量更新?
RAG 中如何实现向量数据库的增量更新?
这道题考的是 RAG 系统中向量数据库的动态维护能力。面试官想看你能不能把"文档变了,向量库怎么跟着变"这件事讲清楚。
我从五个方面来讲:
- 增量更新是什么——文档 Chunk 化和 Metadata 关联
- 删除策略——软删除与硬删除的选择
- 修改文档——先删后插与更新策略
- 增量加载核心——Hash 指纹判断与记录管理器
- 常见问题与向量数据库对比
1. 增量更新是什么——文档 Chunk 化和 Metadata 关联
先搞清楚一个基本概念:文档进向量库之前,得先切成 chunk。
举个例子,你传了一篇 5000 字的文章进去。系统不会把整篇文章转成一个向量,那样检索精度很差。它会把文章切成 1000 字一段,每段生成一个向量。
每个向量通过 metadata 关联回原始文档。
这个 metadata 就好比图书馆的索引卡,记录了"这本书在哪个书架第几排"。没有它,你就找不到向量对应的文档是什么。
典型 metadata 长这样:
{
"source_id": "doc_001", // 文档唯一标识
"chunk_id": "chunk_003", // 这个 chunk 是第几个
"version": 2, // 版本号,用于判断是否过期
"is_deleted": false, // 软删除标记
"created_at": "2024-06-01", // 创建时间
"content_hash": "abc123..." // 内容指纹
}
source_id 是关键。你删除一篇文章时,不是说"删掉某个向量",而是"删掉所有 source_id = doc_001 的向量"。
这就像图书馆管理:不是按书架位置删书,而是按书名删——书挪了位置你也找得到。
2. 删除策略——软删除与硬删除的选择
删除分两种:软删除和硬删除。
软删除就是打个标记,说"这本书下架了",但数据还在。硬删除则是真正把数据删掉,磁盘空间释放出来。
软删除:update vector set is_deleted = true
硬删除:delete from vectors where id = xxx
为什么要区分?
软删除的好处是"后悔药"。你删错了,还能恢复。业务场景比如:企业知识库通常软删除保留 30 天,之后再物理清理。
硬删除呢?两个场景用得多:
- GDPR 合规:用户要求删除个人数据,必须真的删干净
- 存储紧张:向量库空间不够用了,该删就删
一个重要提醒:Pinecone、Milvus 这些商业向量库,delete 操作默认是软删除。数据标记删了,但空间不释放。你得手动调用 flush 或 optimize 才能真正回收空间。
类比一下:软删除像是把书从书架上拿下来,放进仓库。书还在,但正常流程找不到它了。硬删除就是直接扔碎纸机,什么都没了。
3. 修改文档——先删后插与更新策略
文档要修改怎么办?
记住一个核心原则:向量不能直接更新,必须先删后插。
向量是浮点数组成的,存储在向量索引结构里。你没办法定位到某个向量的第几个维度去修改。这不像关系数据库,一条 update 语句搞定。
所以改文档的正确姿势是:
- 删除旧向量
- 插入新向量
就这么简单。
具体执行有两种策略:
全量替换
删掉这个文档的所有旧 chunk,插入新的 chunk。
适用场景:文档不大,或者变化范围不确定。
旧文档 5 个 chunk → 全部删除 → 插入新 5 个 chunk
差分更新
只处理变化的 chunk。
适用场景:大文档微调。比如一个 1000 页的 PDF,只改了第 5 页的内容。那就只删第 5 页对应的旧 chunk,插入新的。
旧文档 5 个 chunk → 只删除变化的 2 个 → 只插入新的 2 个
差分更新能省不少 embedding 调用成本。大文档场景下,这个优化很明显。
并发冲突怎么办?
两个进程同时修改同一份文档,可能出现这种情况:
- 进程 A 删了 chunk_1,准备插入新的
- 进程 B 也删了 chunk_1,准备插入自己的
结果就是后面的插入覆盖前面的,数据丢了一部分。
解决方案:给文档加 version 字段,乐观锁。
- 读取文档,检查 version = 1
- 修改内容,尝试更新 where version = 1
- 如果更新成功,version 变成 2
- 如果更新失败(version 已经是 2),说明被人改过了,重试
4. 增量加载核心——Hash 指纹判断与记录管理器
现在讲最核心的部分:系统怎么知道哪些文档变了,哪些没变?
答案:Hash 指纹。
把 chunk 的内容加上 metadata 跑一遍 Hash 函数,生成一个唯一值。内容没变,Hash 就不变。内容变了,Hash 一定变。
hash(content + metadata) = "abc123..."
下次加载文档时,重新计算 Hash,和上次记录的对比。
- Hash 相同 → 内容没变,跳过
- Hash 不同 → 内容变了,执行删除 + 插入
这个过程需要一个记录管理器来追踪状态。
LangChain 有 RecordManager,LlamaIndex 有 DocumentStore。它们维护一张表,记录每个文档的:
source_id | content_hash | last_updated | is_deleted
doc_001 | abc123... | 2024-06-01 | false
doc_002 | def456... | 2024-05-20 | true
增量加载有两种模式:
incremental 模式(推荐)
遍历文档 → 计算 hash → 对比记录管理器
├── Hash 没变:num_skipped++
└── Hash 变了:删旧插新
这个模式最大优点是跳过没变化的 chunk,embedding 调用省一大截。
full 模式
删除记录管理器中所有该数据源的向量 → 重新插入全部 chunk
适合数据一致性要求极高的场景,但效率低。
运行完增量加载,你会得到两个关键数字:
- num_skipped:多少个 chunk 因为没变化被跳过了
- num_deleted:删掉了多少旧向量
这两个数字能帮你监控增量更新的效果。如果 num_skipped 很少,说明文档更新太频繁,可能需要优化更新策略。
5. 常见问题与向量数据库对比
问题一:向量孤岛
向量库里躺着向量,但 metadata 映射表里找不到对应关系。
原因:向量库和业务数据库不同步。比如你直接删了向量库里的数据,但业务系统的映射表没更新。
后果:向量还在,但永远检索不到,因为没人知道它属于哪个文档。
解决方案:
- 定时扫描向量库,检查孤岛数据
- 记录管理器和不一致告警机制
- 所有删除操作必须走统一接口,不能直接操作向量库
类比:图书馆有这本书,但索引卡丢了。这本书等于不存在。
问题二:重复插入
同一份文档插入两次,向量库里出现两套相同的 chunk。
原因:没有做好幂等性检查。
解决方案:
- 插入前检查 source_id 是否已存在
- 用 unique 约束保证 source_id 唯一
- 增量加载时先用 Hash 过滤
问题三:Chunk 边界错乱
语义相关性高的内容被强行切开了。比如"深度学习是机器学习的子领域"这句话,前半句在 chunk_1,后半句在 chunk_2。检索时只拿到前半句,理解就歪了。
解决方案:
- 按语义切片,不要按固定字数切
- 相邻 chunk 之间保留 overlap
- 用 sliding window 机制
向量数据库对比
不同向量库的增量更新能力有差异:
| 数据库 | 删除方式 | 数据清理 | 一致性特点 |
|---|---|---|---|
| Pinecone | 软删除,需 flush | 自动后台清理 | 最终一致,延迟低 |
| Milvus | 软删除,需手动 flush | 手动触发 compact | 可配置一致性级别 |
| Weaviate | 软删除 | GC 自动清理 | 强一致,延迟稍高 |
| Qdrant | 硬删除直接生效 | 无需额外操作 | 最终一致 |
选型建议:
- 快速上手:Pinecone,全托管,运维省心
- 高性能自托管:Milvus,支持分布式,扩展性强
- 轻量部署:Qdrant,单机版跑起来很简单
面试怎么答
基础版(能过的回答)
增量更新包含增删改查四种操作。核心是:增和改都是插入新向量,删是删除对应向量。删除分软删除和硬删除,企业知识库通常软删除保留一段时间再物理清理。修改文档必须先删后插,不能直接更新向量。我用 metadata 里的 source_id 关联文档和向量,删除时通过它定位所有相关向量。
这段话把基本概念讲清楚了,能过。
加分版(让面试官眼前一亮)
增量更新的难点在大文档差分更新和并发控制。我用差分更新策略,只处理变化的 chunk,比如 1000 页 PDF 只改了第 5 页,就只更新第 5 页的向量,省掉 99% 的 embedding 调用。并发冲突用乐观锁解决,给文档加 version 字段,更新时检查 version 是否匹配。我还用 Hash 指纹判断增量,incremental 模式下没变化的 chunk 直接跳过,监控 num_skipped 和 num_deleted 来评估更新效率。另外向量孤岛问题需要定时扫描,业务删除必须走统一接口,不能直接操作向量库。
这段话展示了实际工程经验,深度够了。
一句话总结
向量数据库的增量更新核心是:文档切片 + metadata 关联 + 先删后插 + Hash 指纹判断增量。
