首页 / 资讯中心 / 文章详情

RAG知识库增量更新:如何同步变更而不重建全量索引

RAG知识库增量更新:如何同步变更而不重建全量索引 ★ FEATURED ARTICLE
授权与合规声明本文全部操作对象均为自建隔离环境与已获授权的目标涉及安全测试或以他人系统为对象的环节必须以取得合法授权为前提。未经授权的测试违反《中华人民共和国网络安全法》与《刑法》相关条款须承担相应法律责任。本文只讲可复现的技术实操与工程落地不含任何攻击步骤、利用载荷与绕过手法请勿将文中任何内容用于非自有、未获授权的系统。第1章 冷启动之后增量同步才是日常很多 RAG 教程止步于建库切分文档、算向量、写进向量库检索能跑通就算完事。但真实业务里建库只是开始。线上文档天天在变——产品手册改了一句话、工单模板新增一条、过期政策被撤下。如果每次变化都重跑全量索引问题会接踵而至。全量重建意味着重新读取所有源文件、重新切分、重新调用 embedding 接口、重新写入向量库。这个过程耗时长、调用外部模型的费用高而且在重建期间旧索引还在被线上查询使用新索引没建完之前检索结果可能不完整等于在建库的同时打断了自己的服务能力。所以真正要解决的问题是知识库冷启动完成之后如何只处理发生变化那一小部分让存量索引原地复用。这就是增量同步。1.1 增量同步要解决的三个问题少花钱只对变化的文档调用一次 embedding而不是对全量文档重复调用。不断服不重建全表线上检索始终可用写入和查询互不阻塞。可追溯每次同步都能说清哪份文件、哪个版本、改了什么方便出问题时回滚和校验。1.2 一个核心判据增量同步能不能触发取决于一个简单的事实源文档的指纹有没有变。指纹没变就什么都不做指纹变了才进入增/改/删的处理分支。这个指纹就是下一章的主角。第2章 文档级 chunk 指纹差量比对的基础增量同步的前提是能快速回答哪些文档变了。最朴素也最可靠的做法是给每个源文档算一个指纹fingerprint并把它和索引里已存的指纹做比对。指纹的构成建议包含两部分内容哈希和修改时间mtime。内容哈希对文档正文或归一化后的字节流做 hash如 SHA-256。内容一字未动哈希就不变。修改时间部分场景下文件被另存为或重导出内容可能没变但 mtime 变了把 mtime 纳入指纹可以避免漏掉这类内容相同但版本认为已变的情况。反过来若你只信内容、不希望 mtime 抖动触发重建也可以只用内容哈希——按团队约定即可。比对时拿当前源文件的指纹集合和索引里记录的指纹集合求差集比对结果含义应执行的操作源有、索引无新增文档插入新增 chunk源有、索引有但指纹不同变更文档覆盖重算 chunk源无、索引有删除文档软删除标记下面是一段计算指纹、并用指纹做差量比对的示意代码。⚠️ 代码待验证importhashlibimportosfromdataclassesimportdataclassdataclassclassDocFingerprint:doc_id:strcontent_hash:strmtime:floatdefcompute_fingerprint(doc_id:str,path:str)-DocFingerprint:withopen(path,rb)asf:rawf.read()content_hashhashlib.sha256(raw).hexdigest()mtimeos.path.getmtime(path)returnDocFingerprint(doc_iddoc_id,content_hashcontent_hash,mtimemtime)defdiff_fingerprints(source:dict,indexed:dict):返回三类差量新增 / 变更 / 删除to_add[kforkinsourceifknotinindexed]to_update[kforkinsourceifkinindexedandsource[k]!indexed[k]]to_delete[kforkinindexedifknotinsource]returnto_add,to_update,to_delete指纹应当随 chunk 一起写入向量库作为元数据字段否则下一次比对时无据可查。这一点在第4章展开。第3章 增、改、删三种操作的落地差量比对给出了三类待处理文档接下来分别落到向量库上。这里的关键诉求是只动变化的部分不碰存量。3.1 新增文档只对新增 chunk 做 embed upsert新增文档不需要读存量。切分出 chunk 后逐个算向量再用 upsert 写入。upsert 的语义是有则更新、无则插入首次写入等价于插入且天然幂等——同一批新增重复执行也不会产生脏数据。3.2 变更文档重算对应 chunk覆盖旧向量文档内容变了旧 chunk 的向量已经失效必须重算。推荐做法是软删除旧 chunk 写入新 chunk按doc_id找到该文档对应的旧 chunk 主键集合先打待删除标记或直接逻辑删除对新的 chunk 切分结果算向量并写入确认新 chunk 写入成功后再物理清理旧 chunk。先写新、后清旧能保证检索在切换期间至少能命中旧版或新版之一不会出现两者都暂无的空窗。3.3 删除文档软删除 定期物理合并文档从源里消失不代表要立刻从向量库里物理抹掉。直接物理删除有两个隐患一是删除操作若和正在进行的查询并发可能短暂影响一致性二是误删后难以恢复。更稳妥的是软删除给 chunk 打一个deletedtrue的标记检索时过滤掉带标记的条目用户侧就已经看不到这份文档了。随后由后台任务在业务低峰期做物理合并/压缩真正回收空间。操作向量库动作是否动存量检索可见性新增upsert 新 chunk否立即可见变更软删旧 写新仅该文档切换期旧/新版之一删除软删标记仅该文档立即可见性关闭第4章 元数据标记让每一次同步都可回溯增量同步跑久了最怕说不清当前索引里每一块到底对应源文件的哪个版本。解决办法是把元数据写进每个 chunk。建议每个 chunk 至少带以下字段doc_id来源文档标识chunk_index该文档内的分块序号便于定位version文档版本或指纹摘要updated_at最近一次同步时间deleted软删除标记布尔source_path来源文件路径便于回溯原始内容。有了这些字段遇到检索结果怪怪的这类问题可以直接按doc_id拉出该文档的全部 chunk核对其version与updated_at判断是不是某次同步没生效。元数据也是后面一致性校验的输入。第5章 向量库能力对照Milvus 与 pgvector增量同步最终要落到具体向量库的原生能力上。下面以 Milvus 和 pgvector 两种常见选型为例看增/改/删各自怎么落地。5.1 MilvusMilvus 的主键primary_key天然支持upsert传入带主键的实体即可插入或覆盖删除则通过delete按主键表达式完成。对变更文档先按doc_id查出旧主键列表再delete然后upsert新实体。5.2 pgvectorpgvector 是 PostgreSQL 的扩展向量只是表里的一列。增/改/删本质是标准 SQLINSERT、UPDATE、DELETE配合WHERE doc_id ...即可把操作收敛到单个文档。对变更文档先UPDATE ... SET embedding..., version... WHERE doc_id... AND chunk_index...或直接先DELETE再INSERT。能力Milvuspgvector新增upsert实体INSERT变更覆盖upsert主键覆盖UPDATE向量列删除delete按主键表达式DELETE WHERE软删除实现加deleted字段过滤加deleted布尔列过滤主键/定位显式primary_key表主键doc_idchunk_index⚠️ 代码待验证# Milvus变更文档先删旧再写新frompymilvusimportCollection collectionCollection(rag_kb)defsync_doc_milvus(doc_id,new_entities):# 软删旧 chunkcollection.delete(exprfdoc_id {doc_id})# upsert 新 chunk主键覆盖语义collection.upsert(new_entities)collection.flush()⚠️ 代码待验证# pgvector按 doc_id 变更覆盖importpsycopg2defsync_doc_pg(conn,doc_id,new_rows):curconn.cursor()cur.execute(DELETE FROM rag_chunks WHERE doc_id %s,(doc_id,))forrowinnew_rows:cur.execute(INSERT INTO rag_chunks (doc_id, chunk_index, embedding, version, updated_at, deleted) VALUES (%s, %s, %s, %s, now(), false),row,)conn.commit()配套资料这一章讲到的实操要点我整理成了可运行的示例与对照表放在资料包里扫码即可获取第6章 一致性校验索引与源文件对账增量同步是按指纹差量跑的如果某次任务中途失败、或embedding写入丢了一块索引和源就会慢慢对不上。所以需要定期做一致性校验比对索引里的 chunk 数和源文件应当产生的 chunk 数。校验项比对双方不一致时的处理chunk 总数索引doc_id分组计数 vs 源重新切分计数告警触发重算该文档指纹一致性索引versionvs 源当前指纹标记疑似陈旧进入变更分支软删残留deletedtrue占比是否过高排期物理合并回收空间孤儿 chunk索引存在但源已删除且未软删补打软删标记下面是一段对账脚本的骨架按doc_id分组计数后比对。⚠️ 代码待验证# 简易对账导出索引 chunk 计数与源计数后比对示意python -PY from collections import Counter index_counts {doc_a: 12, doc_b: 8} # 来自向量库查询 source_counts {doc_a: 12, doc_b: 9} # 来自源重新切分 for doc_id, n in source_counts.items(): if index_counts.get(doc_id) ! n: print(f[ALERT] {doc_id} 不一致: 索引{index_counts.get(doc_id)} 源{n}) PY校验发现不一致时不要盲目全量重建先定位到具体doc_id只对那个文档重跑增量分支即可。第7章 什么时候必须全量重建差量同步很强但它救不了结构性变化。以下三类情况增量同步无能为力必须全量重建触发条件为什么差量同步不行是否必须全量重建embedding 模型更换旧向量语义空间与新模型不兼容是向量维度变化新旧维度不一致无法原地覆盖是索引 schema 大改字段结构、主键策略变了是个别文档内容变更只影响局部差量即可否判断的口诀很清晰差量同步触发判据文档content_hash/mtime变化 → 进入增/改/删分支。必须全量重建判据embedding 模型变更、向量维度变更、索引 schema 大改 → 这三种任何一条成立就别犹豫全量重建。全量重建也要讲究先在隔离集合如另一张表或另一个 collection建好新索引校验通过后再把线上查询流量切换到新索引再下线旧索引。这样即使重建耗时较长也不影响线上检索。配套资料这一章讲到的实操要点我整理成了可运行的示例与对照表放在资料包里扫码即可获取附表 A本文引用事实与出处对照表事实出处/依据本文位置embedding 模型或维度变更后旧向量无法直接复用需重建通用向量检索工程常识待验证具体版本行为第7章Milvus 支持upsert与delete原语Milvus 官方文档能力描述截至 2026-10-27待验证具体版本 API第5章pgvector 以 PostgreSQL 扩展形式提供向量列与 SQL 操作pgvector 项目说明截至 2026-10-27待验证第5章软删除定期合并是控制删除成本与一致性的常见做法通用数据库/索引运维常识待验证第3章、第6章一致性校验需比对索引与源文件计数通用数据对账工程实践待验证第6章注本文涉及的具体版本号、命令行输出、性能数字均未实测按 brief 要求统一标注待验证请以官方最新公布为准。附表 B术语速查表术语含义冷启动知识库首次从源文档全量建索引的过程chunk文档被切分后的最小检索单元指纹fingerprint由内容哈希与 mtime 构成的文档标识用于差量比对upsert有则更新、无则插入的写入语义软删除打标记而非物理抹除检索时过滤掉一致性校验定期比对索引与源文件发现不一致并告警全量重建丢弃旧索引、从源重新建库用于结构性变更写在最后这篇用到的资料写这篇文章时我把里面对应的实操示例和踩坑记录顺手整理了几份配套的东西全套 AGI 大模型学习路线从基础到实战的系统性路径避免零散摸索Claude Skills 全指南把重复性工程动作沉淀成可复用技能LangGraphMCP 智能体实战视频跟着敲一遍就能跑通的智能体项目资料是我自己整理的放在下面这个码上扫码即可获取资料较多建议先看「全套 AGI 大模型学习路线」再挑一个实战项目跟练。
阅读完成 · 觉得有帮助?
咨询建站