文章

LLM 检索索引更新生产实战:用蓝绿索引与增量回灌避免 RAG 质量回退

本文讲解如何治理 RAG 检索索引更新,覆盖蓝绿索引、双写、增量回灌、别名切换、质量抽检和回滚策略,帮助团队在文档、Embedding 模型或切块规则变化时降低检索质量回退风险。

背景问题:RAG 的质量事故经常发生在索引更新阶段

很多团队把 RAG 的稳定性问题归因于模型幻觉、Prompt 不稳定或向量数据库性能不足。但在生产环境里,另一类更隐蔽的问题来自索引更新

文档被重写、权限字段被调整、切块策略改变、Embedding 模型升级、元数据清洗规则更新,都会改变检索结果。如果这些变化直接写入线上索引,用户可能看到三类故障:

  • 混合版本检索:一部分文档已用新 Embedding 或新 chunk 规则写入,另一部分仍停留在旧规则。Query 返回的上下文来自不同版本,排序分数不可比,最终答案出现忽高忽低的质量波动。
  • 删除与更新滞后:文档已失效但旧 chunk 仍留在索引中,或正文已更新但元数据过滤条件未同步。RAG 生成结果可能引用过期条款、旧价格、旧权限或错误证据。
  • 切换不可回滚:新索引覆盖上线后才发现召回下降,却没有保留旧索引、别名、回放数据和失败队列,只能边修边等重新构建完成。

因此,生产级 RAG 不应把索引更新看成一次普通写入,而应把它当成一次可验证、可灰度、可回滚的数据发布

核心原理:把索引从”存储对象”变成”发布对象”

一个可治理的 RAG 索引至少包含四层状态:

层级含义关键字段示例
数据版本chunk 来自哪批文档、哪次清洗、哪个权限快照document_versionchunk_versionsource_updated_atacl_versioningestion_run_id
向量版本由哪个 Embedding 模型、维度、归一化方式和距离度量生成embedding_modeldimensionnormalizationdistance_metric
索引版本查询流量指向哪一套 collection/index/namespace/aliaskb_prod_v12kb_prod_v13kb_prod_current
发布版本哪个业务环境、租户或用户组使用哪个索引版本应用配置、网关路由、alias 映射

这四层拆开后,索引更新就不再是”写几条向量”,而是一个发布流程:构建新版本 → 保持增量同步 → 离线回放 → 影子验证 → 切换入口 → 保留回滚窗口。

蓝绿索引:不要在旧索引上直接大修

蓝绿索引的思路很直接:线上查询继续访问旧索引,后台构建一套新索引。新索引构建完成并通过验证后,再把查询入口切过去。

以向量数据库为例,有两种实现方式:

双 collection / 双 index

例如 kb_prod_v12 是当前线上版本,kb_prod_v13 是新版本。应用层只访问逻辑名 kb_prod_current,底层通过配置或别名把它指向某个真实集合。

# 应用层始终访问逻辑别名
GET /search?index=kb_prod_current&query=...

# 底层映射(可动态切换)
kb_prod_current → kb_prod_v12  # 旧索引
kb_prod_current → kb_prod_v13  # 切换后

同 collection 多向量字段

如果数据库支持 named vectors,可以在同一条记录里保留旧 Embedding 和新 Embedding,查询时通过 using 参数选择向量字段。这种方式更省存储,也便于回滚,但前提是索引从设计之初就支持多向量模型。

蓝绿索引的核心价值不在于”多一份数据”,而在于把构建风险线上查询隔离开。新索引可以慢慢回灌、抽检、回放,旧索引继续稳定服务。

增量回灌:双写只是开始,失败补偿才是关键

蓝绿切换期间,系统面临一个基本矛盾:历史数据要重建,新数据还在不断进入。

解决办法通常是 全量回灌 + 增量双写 + 失败补偿 的组合:

  • 全量回灌:把旧索引中的历史文档重新解析、切块、Embedding 并写入新索引。
  • 增量双写:迁移期间的新文档、更新和删除事件同时写入旧索引和新索引。
  • 失败补偿:处理超时、Embedding 限流、写入失败和部分成功。

一个简化的事件结构如下:

{
  "event_id": "evt_20260704_000123",
  "document_id": "policy_9812",
  "document_version": "2026-07-04T10:20:00Z",
  "operation": "upsert",
  "target_index_version": "kb_prod_v13",
  "embedding_model": "text-embedding-model-v2",
  "chunker_version": "chunker_2026_07",
  "retry_count": 0,
  "trace_id": "rag-ingest-abc123"
}

这类事件应进入队列,由 ingestion worker 处理。worker 不仅负责写入向量数据库,还要把每一步状态落库:

已解析 → 已切块 → 已生成向量 → 已写入 → 已校验 → 已失败

如果只做双写而不做失败队列,迁移后的新索引很容易出现”看起来完成,实际漏写”的问题。

删除一致性:RAG 更新里最容易被低估的环节

很多索引更新方案只讨论 upsert,却没有认真处理 delete。在 RAG 里,删除比新增更敏感——新增漏掉最多导致召回不足,删除漏掉可能导致系统继续引用过期合同、撤回公告或用户无权访问的内容

建议把删除事件作为一等公民:

{
  "document_id": "contract_2025_old",
  "operation": "delete",
  "delete_scope": "all_chunks",
  "acl_version": "acl_20260704",
  "reason": "source_deleted",
  "effective_at": "2026-07-04T12:00:00Z"
}

上线前至少验证三件事:

  1. 目标索引中是否还存在该文档的 chunk
  2. 搜索结果是否还能召回该文档
  3. 生成答案是否还能引用该文档作为证据

如果业务对权限敏感,还要区分内容删除权限撤销。权限撤销不一定删除向量,但必须确保检索过滤条件立即生效。

质量验证:不要只看写入成功率

索引更新最常见的误区是把 upsert success 当成上线标准。写入成功只说明数据进入了索引,不说明检索质量稳定。

上线前至少应做四类验证:

验证类型核心内容关注指标
完整性验证文档数、chunk 数、删除数、失败数、重复主键、空向量、异常维度、元数据缺失差异率 < 阈值
关键 Query 回放高频问题、长尾问题、权限敏感问题、历史故障问题top-k 重合度、证据覆盖率、答案差异
影子流量异步把线上 Query 打到新索引召回差异、延迟、空结果率、过滤命中率
人工抽检高风险场景抽样(法律、金融、医疗、保险、企业制度)证据版本、权限、段落准确性

切换策略:用别名或逻辑入口完成原子切换

业务代码不应硬编码真实索引名,而应访问逻辑入口(如 kb_prod_current)。底层可通过向量数据库 alias、应用配置中心或检索服务路由完成映射。

切换时记录以下元信息:

release_id:      rag_index_release_20260704
from_index:      kb_prod_v12
to_index:        kb_prod_v13
switch_mode:     alias_flip
started_at:      2026-07-04T12:00:00Z
owner:           search-platform
rollback_index:  kb_prod_v12
validation_report: rag_eval_report_20260704

如果向量数据库支持原子 alias 操作,应优先使用。否则在应用层做单点配置切换,确保配置发布可审计、可回滚。

回滚策略:旧索引至少保留一个观察窗口

新索引上线后不要立即删除旧索引,至少保留一个观察窗口(24 小时、72 小时或一个完整业务高峰周期),具体时长取决于业务风险和索引构建成本。

回滚不是简单把配置改回旧索引,还要处理上线后发生的新写入:

  • 如果切换后仍双写旧索引:回滚比较直接,旧索引数据完整。
  • 如果上线后停止旧索引写入:需评估旧索引缺失的增量事件,决定先补齐还是接受短期滞后。

稳妥做法:切换后继续保持一段时间的双写,等新索引稳定后再停止旧索引写入并回收资源。

适用场景

这个方案尤其适合以下场景:

  1. Embedding 模型升级:新旧模型的维度、分布、归一化和相似度表现不同,不应在同一个向量字段里混合。
  2. 切块规则调整:从固定长度切块改成标题感知、版面感知或语义切块后,chunk 数量、边界和证据粒度都会变化。
  3. 大规模文档重建:导入新知识库、重跑 OCR、修复解析器、补充元数据字段。
  4. 权限模型变化:ACL、租户边界、组织层级、数据域过滤条件变化时,必须验证检索不会越权。
  5. 混合检索策略更新:BM25、稀疏向量、稠密向量、reranker 权重变化时,需对新旧检索链路隔离验证。

常见误区

误区一:只要向量数据库支持 upsert,就可以直接在线更新

Upsert 是写入机制,不是发布机制。它不能自动保证回灌完整、删除一致、版本隔离和质量不回退。

误区二:只比较平均召回率,不看关键问题

平均指标可能掩盖关键业务问题。生产验证必须保留高风险 Query 集合——高价值客户、合规条款、产品价格、理赔规则、权限边界等。

误区三:忽略元数据版本

很多 RAG 事故不是向量错了,而是元数据过滤错了。tenant_iddoc_statuseffective_dateacl_groupsource_type 等字段需要和向量一起纳入版本检查。

误区四:上线后立即释放旧索引

这会让回滚变得昂贵。旧索引至少应该保留到新索引经历足够的真实流量观察。

上线检查清单

上线前建议逐项确认:

  • 新索引的文档数、chunk 数、删除数和旧索引差异可解释
  • 所有向量维度、距离度量、归一化方式与 Embedding 模型一致
  • 增量事件队列无长期积压,失败事件进入 dead-letter queue
  • 删除事件、权限撤销事件和文档失效事件已单独验证
  • 关键 Query 回放通过,空结果率和召回差异在阈值内
  • 影子流量已覆盖真实高峰和高风险租户
  • 别名或逻辑入口切换可审计、可回滚
  • 旧索引保留时间、双写停止时间和资源回收时间已明确
  • 监控已覆盖:查询延迟、空结果率、top-k 差异、Embedding 失败率、写入延迟、索引版本分布

FAQ

RAG 索引更新一定要做蓝绿吗?

不一定。小规模、低风险、只改少量元数据的更新可以用普通 upsert 或批量 update。但只要涉及 Embedding 模型、切块规则、权限模型或大规模重建,就应该按蓝绿或多版本方式治理。

新旧索引如何比较质量?

可以从四个角度比较:top-k 文档重合度、关键证据是否命中、最终答案是否引用正确证据、用户可见结果是否符合权限和时效要求。不要只看向量相似度分数——不同模型或不同索引配置下分数不一定可比。

如果向量数据库不支持 alias,怎么办?

可以在检索服务层做逻辑别名。业务请求只访问 knowledge_base_idindex_channel,检索服务内部通过配置把它解析为真实索引名。关键是把切换入口集中化,而不是让多个业务系统各自硬编码索引名。

参考资料

常见问题

RAG 索引更新为什么不能直接覆盖线上索引?
直接覆盖会把写入延迟、Embedding 失败、文档删除、切块变化和近实时一致性问题暴露给线上查询,容易造成召回下降、旧知识残留或部分用户看到混合版本。
蓝绿索引和增量回灌有什么区别?
蓝绿索引强调用新旧两套索引隔离构建和切换;增量回灌强调持续把变更文档、历史文档和失败重试补齐到目标索引。生产系统通常需要二者配合。
什么时候应该切换到新索引?
只有当回灌完成率、抽样召回、关键 Query 回放、线上影子流量、删除一致性和回滚路径都通过检查后,才应该把查询流量切到新索引。
如果向量数据库不支持 alias,怎么办?
可以在检索服务层做逻辑别名。业务请求只访问 knowledge_base_id 或 index_channel,检索服务内部通过配置把它解析为真实索引名,关键是把切换入口集中化。