向量库写入反压与索引在线重建切换落地

日期:2026-09-22

一、项目背景

我们的知识库每天都在灌新文档,写入量白天高、夜里低,波动很大。一开始向量库没做写入限流,某次营销活动带起一波集中上传,写入请求把索引进程压满,索引服务直接假死,检索也跟着变慢。更糟的是,那阵子正好要重建索引优化召回,重建期间老索引被占用,检索基本不可用,前端问答大面积超时,工单哗哗地来。

我们事后复盘,根因有两层:一是写入没有反压,高峰把资源吃满;二是重建索引和检索抢同一份资源,没有隔离也没有在线切换。向量库不是只管存,写入和重建都得有节奏,否则一处抖,全文检索都跟着抖,而这个检索又是好几个上游服务的依赖,牵一发而动全身。

二、落地场景

我们采用的向量数据库层支持增量索引和读写分离,但默认配置下写入和检索抢同一份资源。落地场景包括:高吞吐导入时启动写入反压,把超出承载的写入请求排队或限速,保护检索链路;增量索引实时合并,避免全量重建;重建索引走影子副本,建完再原子切换,切换期间老索引继续服务;读写分离,写入高峰检索不受影响。目标是让检索在写入风暴里也保持可用,同时索引新鲜度不掉太多,知识库当天的新文档当天能搜到。

三、关键技术挑战与解决思路

第一关是反压阈值。限得太狠导入慢、业务等不及,限得太松又回到压垮的老路,我们按实例的写入队列长度和 CPU 占用双指标触发反压,留了缓冲带,不是一触即发。第二关是在线切换,老索引在重建时不能停检索,我们改成先建影子索引,校验召回一致后再切流量,切的过程是秒级,业务无感。第三关是读写分离的资源隔离,之前写和读共用线程池,我们拆成两池并设上限,写入再猛也挤不到读。

有个插曲,第一次做影子切换时校验脚本漏了维度字段,切过去召回掉了,我们紧急回切并补了校验项,从此切换前必跑一致性比对,比对不过不让切。那个故障虽然只持续了几分钟,但足够让我们把流程钉死。

四、效果数据

写入反压上线后,索引服务在导入高峰的可用性从约八成提升到接近满格,检索 P95 延迟在写入风暴里基本平稳,不再跟着写入一起抖。索引重建从过去的停检索半小时,变成在线切换秒级完成,期间检索零中断。数据新鲜度延迟控制在分钟级,当天灌的文档当天可搜。我们做了一次极限压测,模拟活动峰值三倍的写入,检索侧依旧稳住。文中数据为项目复盘口径,已做脱敏。

五、可复用经验总结

向量库写入要有反压,这不是性能优化,是稳定性底线,没它写入一高就全员遭殃,检索挂了上游全跟着挂。索引重建千万别停检索,影子副本加在线切换是基本功,老办法停机重建在线上系统里已经不能接受了。切换前的一致性校验不能省,我们那次漏字段的事故,教训就是校验脚本要跟着 schema 走,schema 一变校验就得跟着补。读写资源隔离看着多余,真到写入风暴才知道值,共用线程池是最容易爆的隐藏雷。

向量库这块最大的认知转变,是把检索可用性当成一等公民,而不是性能调优的副产品。过去我们盯着写入速度和召回指标,觉得检索慢一点能忍,直到那次重建停检索把上游全带挂,才明白检索是依赖链的咽喉。现在任何影响检索的运维动作,我们都要求零中断方案,宁可多花功夫做影子切换也不停机。还有,反压的阈值不是配一次就完事,数据规模上去之后,原来的队列上限会显得宽松,我们把它和容量监控绑在一起,超过基线就告警。稳定性这种事,靠一次配置救不了一世,得变成日常巡检的一部分。

还有一个容易被忽略的点,反压本身也会引入写入延迟,业务侧偶尔会抱怨导入变慢。我们当时和写入方约定了分级策略,重要文档走优先通道几乎不限流,普通批量导入才进反压队列,这样既保住了检索,也没把正常导入卡死。这里的关键是别把反压当成对所有写入一视同仁的限速,得区分轻重。另外影子切换那套流程我们后来固化成了标准运维动作,任何索引变更都走影子加一致性校验再加原子切换,再没出现过停检索的事故。回过头看,这次事故虽然痛,但把我们的向量库运维规范整个提了一档,从救火式变成了有流程可依,长期来看是赚的。

最后记一句,反压的队列上限和实例规格要一起评估,不能脱离硬件单独定。我们曾因为升配后没同步调高阈值,结果老阈值反而成了新规格下的瓶颈,写入吞吐上不去查了半天才发现是配置没跟着走。容量和配置要绑定着看,这是运维里最容易忘的一层。

案例片段(已脱敏): 写入反压与在线切换配置片段:write:  backpressure:    enabled: true    trigger_qlen: 5000    trigger_cpu: 85    mode: queue index_rebuild:  shadow: true  switch: atomic  consistency_check: required  read_write_split: true一次写入风暴的处置日志:[backpressure] ingest_qlen=5200, cpu=87, throttled=yes [rebuild] shadow_built=ok, recall_diff=0.3%, switch=atomic_ok [read] p95_latency=stable, outage=0漏字段回切记录(早期事故):[switch] shadow_recall_drop=12%, auto_rollback=yes [fix] added dim_field to consistency_check