日期:2026-08-19
我们做知识库增量更新,运营经常一次导入几十万条文档。第一版没限速,导入任务一启动,向量库写入直接打满 CPU,线上检索抖成心电图,导入任务自己也没跑完,半截数据卡在那。运维半夜被告警叫起,检索服务一度不可用,业务方以为模型又抽风了。我们后来算过,那次导入高峰期检索 P99 从正常的 300 毫秒飙到 2 秒以上,持续了四十多分钟,这段时间内的问答质量肉眼可见地掉,后台好几个长文档问答直接超时,用户侧的反馈比告警还来得快。
我们当时判断,导入和检索在抢同一份算力,不限速就是自残,尤其在向量这种写放大严重的场景,一条文档要算嵌入、建索引,开销是读取的好几倍,导入一猛就压垮在线服务。后来我们给运营做了个导入前自检清单,限速、断点和副本三件事确认过才放行,运营自己也说心里有底,半夜被叫起来也知道不是大事故。
方案分五块。一是写入反压:向量库接收速率超阈值就拒绝并排队,保护在线检索。二是批量导入限速:按 shard 限流,控制每秒写入条数。三是分片写入:大任务拆成小批次,每批带序号。四是失败断点续传:记录已写入 offset,失败从断点接着写。五是导入期检索降级:导入高峰让检索走只读副本,保住可用性。
最磨人的是写入反压的阈值。限太松还是压垮检索,限太紧导入要跑一整夜。我们按在线检索的 P99 预算反推写入速率,导入速率上限设为检索可承受区的七成,留余量,这样检索侧基本无感。
另一个坎是断点续传。第一版重导时从零开始,失败重试等于翻倍写,数据重复一大片,去重都难。后来每批写入成功才推进 offset,失败重跑只补未写批次,导入任务从此可重入,运维半夜被叫起来也不慌,重跑就行。
还有导入期检索降级。完全停检索业务不答应,全量走主库又会被导入拖死。我们让导入期间检索流量切到只读副本,主库专心写,副本延迟几秒对语义检索可接受,用户几乎感知不到。
还有一点容易踩坑,是限速和重试的配合。导入任务失败重试时,如果重试不降速,等于又来一波冲击,检索照样抖。我们给重试也套了同一套限速,并且失败批次退到队列尾部错峰重跑,不让重试变成二次打爆。另外写入反压拒绝时,不能简单丢掉,要进排队缓冲,否则运营看到的是导入莫名其妙少了数据。我们让被拒的批次进入等待队列,等算力松下来再补,导入完整性才有保证。这套机制上线后,运营再没因为导入丢数据来找我们,运维的告警也安静了。我们后来还把导入任务做成可观测的,进度、速率、拒绝数、剩余量全在面板上看得到,哪批卡住一眼能定位,不用再翻日志猜。之前出问题全靠猜,现在出问题直接看面板,排障时间从小时级降到分钟级。说实话,向量库写入这块的水深,我们调了两轮才把阈值定稳,第一轮限太狠导入跑了一整夜,业务方等不及,第二轮松一档才平衡。现在回头看,导入和检索的算力分配,核心就是那一个速率上限,定准了两边都安生。
案例片段(已脱敏): 写入限速与反压(配置节选):
write: rate_limit_per_shard: 1200 # docs/s backpressure: cpu_threshold: 0.75 reject_and_queue: true batch_size: 2000 checkpoint: offset_file: import.offset resume_on_fail: true某次运营导入 42 万条,开启限速后检索 P99 从抖动到 320ms 稳住,导入耗时 6 分钟,offset 机制在第三次批次失败后自动续传,无重复写入,而此前无限速版本同批导入曾让检索不可用好一阵。案例片段(已脱敏): 某次大促前知识库紧急更新,运营分两批导入,第一批二十万条跑完,第二批中途网络抖动断了。按旧逻辑得从头再来,数据会重复一半。新逻辑从 offset 续上,只补了断点的三万条,全程检索没掉链子,大促当天问答正常,没人知道后台刚经历一次导入故障。
导入期间检索 P99 波动从最高 2.1 秒收窄到 350 毫秒内,导入失败率从约 20% 降到 1% 以下。相关告警下降约八成,运维半夜被叫起的次数归零。导入吞吐稳定在每秒约 1100 条,大盘可控。文中数据为项目复盘口径,已做脱敏。
导入和检索抢算力,限速是必须做在前头的那道防线,阈值要由检索预算反推,别凭感觉。断点续传必须做,否则重试就是翻倍写,这坑我们实打实踩过,重复数据清了两天。导入期检索降级走只读副本,比全停或硬扛都稳。说实话,分片大小那块我们还调过好几轮,太大写入阻塞、太小 overhead 高,最后两千条一批在数字上最舒服,这部分建议新接的项目直接抄这个值起步。导入这类后台任务,平时没人盯着,一旦出问题就是半夜告警,把限速、断点、降级这三件套做齐,才敢让它真正无人值守地跑。