向量索引的零停机重建
换 embedding 模型、改切分、重建索引——这些操作在生产 RAG 里很头疼,搞不好就停服或搜不准。本文讲清向量索引零停机重建的几种方案:蓝绿索引、双写、影子流量、重建+切换,以及增量更新避免全量重建,让你的 RAG 改 embedding/改结构时用户无感。
本文要点
- 主题聚焦:RAG、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
做 RAG 做久了,迟早撞上这一刻:embedding 模型想换、切分策略要改、或者历史数据大面积更新。这些动作的共同点——都得重建向量索引。而生产环境里重建索引,最怕两件事:一是停服,二是切完发现召回变差。
这篇把零停机重建的几种打法讲透。
一、为什么向量索引要重建
先理清哪些场景非重建不可:
- 换 embedding 模型。 老模型召回不行了,想换 BGE-M3 或 OpenAI 新版。新模型的向量维度/空间和老的不兼容,老向量完全作废,只能全量重算。
- 改切分策略。 从固定 512 token 改成语义切分,chunk 的边界变了,每个 chunk 的内容变了,对应的向量也必须重算。
- 数据大改。 比如知识库重构、批量去重、元数据清洗后需要重灌。
为什么不能停服重建? 因为重建不是几秒钟的事。百万级文档,重新 embed + 建索引,可能要几十分钟到几小时。这段时间你要么查询全部 503,要么查的是"半新半旧"的脏数据,结果错乱。线上 RAG 哪能这么干。
所以核心矛盾是:新索引要花时间建,而建的过程里线上还得正常服务。 下面几种方案都是围绕这个矛盾展开。
二、方案一:蓝绿索引——原子切换
最经典的思路,借自部署里的蓝绿发布。同时维护两套索引,一套线上(蓝),一套在建(绿)。
[查询路由] ──> 蓝索引(v1,线上)
绿索引(v2,后台全量重建中)
...重建完成,校验通过...
[查询路由] ──原子切换──> 绿索引(v2) 蓝索引下线
流程:
- 开一个新集合/新命名空间(如
docs_v2),后台跑全量重建,把所有文档用新 embedding/新切分重新灌进去。 - 重建期间,查询和写入都还走 v1,绿索引对用户不可见。
- 绿索引就绪 + 校验通过后,一次性把查询路由切到 v2(改个配置/灰度开关)。
- 观察,确认稳定后下线 v1。
优点:切换是原子的,用户感知不到中间态;回滚也简单——切回 v1 即可(v1 还没删)。
坑:重建期间的新写入只进了 v1,没进 v2。所以切换前要补增量——把 v1 上重建期间新增/修改的文档同步到 v2,否则会丢数据。常用做法是切换前暂停写入几分钟做一次增量补齐,或用 CDC/变更日志追赶。
适合数据量大、能接受短暂写暂停、想要干净切换的场景。
三、方案二:双写——平滑过渡
不想切换有跳变?那就让两个索引同时承接流量,慢慢过渡。
写入 ──┬──> v1 索引(老 embedding)
└──> v2 索引(新 embedding)
读取 ──> v1(过渡期) ──验证 v2 质量对齐──> v2
流程:
- 上线双写代码:每次写入/更新文档,同时写 v1 和 v2(用各自的 embedding)。
- 后台把历史存量全量灌进 v2。
- 存量灌完后,v1 和 v2 数据一致(双写保证了增量同步)。
- 读流量从 v1 灰度切到 v2(5% → 50% → 100%)。
- 全量切到 v2 后,停 v1 的双写,下线 v1。
优点:无停机、无跳变、读切换可灰度,质量有保障。
坑:
- 写入成本翻倍(两次 embedding + 两次写)。
- 代码复杂度上升,要维护双写逻辑和一致性(写 v1 成功、写 v2 失败怎么办?要有补偿)。
- 过渡期可能持续较久。
适合不能停写、要平滑灰度、对一致性要求高的场景,比如面向 C 端的搜索产品。
四、方案三:影子流量——先验证再切
如果你对新索引的质量没底(换了 embedding 或切分,召回可能变好也可能变差),别直接切用户流量。先让新索引跑影子查询,和线上结果对比,验证没问题再切。
用户查询 ──> v1 索引 ──> 返回结果给用户(线上)
└─(异步)─> v2 索引 ──> 结果落日志,不返回
离线对比 v1 vs v2 的 Top-K 重合度 / 点击率 / 人工抽检
流程:
- v2 全量重建完毕(蓝绿或双写都行)。
- 线上查询同时发给 v1(同步,返回用户)和 v2(异步,结果存日志不给用户)。
- 离线对比两套结果:Top-K 重合度、召回质量(用评估集)、响应延迟。
- 若 v2 质量对齐或更优,再切流量;若变差,先调,不切。
价值:这是唯一能在切流量前真实看到新索引在线上 query 分布下的表现的方法。评估集再全,也不如真实流量。
成本是多一份异步查询的计算开销,但比起"切完发现召回崩了、线上炸了"的代价,值。
五、增量更新:多数时候不必全量重建
冷静想想,真的需要全量重建吗?
很多团队一拍脑袋就全量重算,其实改动的只是小部分数据。比如:
- 知识库只更新了几百篇文档 → 只重算这几百篇的向量。
- 切分策略没变,只是补了元数据 → 索引根本不用动。
- 只有一批旧文档格式错误需要重切 → 重切这批即可。
全量重建是最后的手段,不是默认选项。 设计 RAG 时就要为增量留好口子:
- 每个文档/chunk 带版本号和 embedding 模型标识(如
embed_model: bge-m3,embed_version: 2)。 - 查询时只走当前版本;升级时按需批量重算未带新标识的文档,小步推进。
- 写入永远是新版本,存量慢慢追赶。
把"重建"拆成"持续的小批量迁移",能避免一次性大动作的风险。只有 embedding 维度变了或切分整体重构,才不得不全量。
六、换 embedding 的特殊处理
换 embedding 是重建里最重的一种,因为维度一旦变,向量空间完全不兼容,没法增量、没法混用——老向量和新向量在一个索引里是错的。
几个实战提醒:
- 维度变 = 全量重建,没有捷径。 所以选 embedding 时就要慎重,锁版本(前一篇 embedding 选型讲过)。换 embedding 的代价比换大模型还高。
- 过渡期不能"混查"。 同一个查询不能一会儿用老 embedding 编码、一会儿用新的,因为索引里的向量是新 embedding,查询却用老的,算出来的相似度全错。查询和索引的 embedding 模型必须严格一致,这是铁律。
- 若非换不可,优先用蓝绿或双写,把整个索引当作不可分割的整体切换,绝不要新老混存。
- 一个偷懒但有效的策略:新 embedding 如果维度更高、质量更好,值得花这次重建成本,但要选好时机(低峰期、备好回滚、跑过影子流量)。
七、工程要点:成本、回滚、监控
不管选哪种方案,这三件事都得想清楚:
1. 成本。 全量重建意味着把全库文档重新 embed 一遍——embedding API 调用或 GPU 推理成本不低。百万文档重算,按商业 API 可能几百到几千块。提前估算 token 量,低峰期跑,必要时分批限流,别把账单和线上延迟打爆。
2. 回滚。 切换前必须想好"切完出问题怎么回去":
- 蓝绿:旧索引保留 N 天,出问题切回。
- 双写:读流量灰度,发现问题立刻把灰度回退到 0%。
- 影子流量本身就是回滚的安全网——没切就没什么好回滚的。
- 新旧索引别急着删。至少观察一周稳定后再下线,给自己留后悔药。
3. 监控切换前后的质量。 切换不是终点,监控才是。盯这几个信号:
- 召回指标:线上埋点,对比切换前后的 Top-K 点击率、无结果率、用户二次搜索率。
- 评估集回归:固定评测集每天跑,切换那天看曲线有没有掉。
- 延迟和错误率:新索引的查询延迟、超时率是否正常。
- 离线对比:影子流量日志里的重合度、人工抽检准确率。
切换当天最好有人盯着,出问题第一时间回滚,别硬扛。
小结
向量索引重建是 RAG 运维里最危险的一类操作,但有几条清晰的原则:
- 先判断是否真的要全量——多数时候增量更新就够了,全量重建是最后手段。
- 蓝绿索引适合能短暂停写的场景,原子切换、回滚简单。
- 双写适合不能停写的线上服务,平滑灰度,代价是写入翻倍。
- 影子流量是切流量前的安全网,验证新索引在线上 query 下的真实表现。
- 换 embedding 最重:维度变就全量重建,查询和索引的 embedding 必须严格一致,新老不能混。
记住一句:索引重建不可怕,可怕的是没有回滚路径就敢切流量。 把方案、校验、回滚、监控四件事都备齐,重建就是一次 boring 的例行操作——而 boring,正是生产系统最好的状态。
Related