3133 字
约 10 分钟
22
S20-A08 · 向量索引运维:重建、增量与一致性

知识库文档改了,检索结果里还是上周的段落,点进去原文已经不存在。排查三小时,最后发现不是召回不准,是索引里躺着旧向量——向量库对"更新"这件事的支持程度,和倒排索引完全不是一个量级。

向量索引的运维目标也不是"更新得快",而是三条:能删干净、能被核对、能在精度退化时回滚。重建只是手段,代价结构才是决策依据。


1. 写入到可检索的三段延迟

段 发生什么 延迟量级 谁负责
嵌入计算 调模型把文本变向量 百毫秒级/条,批量可摊薄 写入侧
索引构建 把向量插进图或聚类结构 HNSW 近实时,IVF 需训练与分配 向量库
刷新可见 段合并、可见性切换 秒级到分钟级,按实现核对 向量库

三者相加才是"写了多久能搜到"。多数"改了搜不到"的工单卡在第二段:向量进了库没进索引,或进了索引但查询仍走旧段。

2. 与倒排索引的根本差异

维度 倒排索引(ES/Lucene) 向量索引(HNSW/IVF)
更新语义 文档级原地替换(删旧 + 写新,段内) 插入新向量;删除多为标记
删除代价 标记删除 + 段合并时物理清除 标记后仍占内存,影响图连通性
增量友好度 高,refresh 后即可见 中,HNSW 可增量插入,IVF 需重训练
一致性手段 版本号、refresh、translog 外部版本字段 + 双写/重建
精度随更新退化 轻微 明显(墓碑与碎片积累)

取向由此确定:倒排索引能"边写边搜",向量索引必须假设索引是有寿命的易腐资产,需要定期重铸。

3. 索引类型的三角与参数调节方向

类型 精度 内存 构建时间 适用规模 更新友好
暴力检索(Flat) 精确 高(全量原始向量) 无 十万级以下 最好
HNSW 高,可调 很高(图边占大头) 中 百万到亿级 好,可增量插入
IVF-Flat 中,依赖 nlist 与 nprobe 中 需训练聚类 百万级以上 差,分布变化需重训
IVF-PQ 低到中 低(量化压缩) 长 亿级以上 差

三者互斥:先固定一个不可让步的约束(通常是内存),其余两个接受劣化。

参数 调大 调小 过度调节的后果
M(HNSW 每层边数) 召回升、内存涨、插入变慢 内存省、召回降 M 过大,构建时间与内存双爆,收益趋零
efConstruction 图质量好、构建慢 构建快、图变差 一次性成本,收益在构建期就定型
efSearch 召回升、查询延迟线性涨 延迟低、召回掉 尾延迟不可预测,P99 先崩
nlist(IVF 簇数) 每簇更小、精度升、训练变慢 精度降、扫描少 与数据量不匹配时,单簇过大等于暴力
nprobe 扫描簇多、召回升 快、漏检多 调到接近 nlist 就退化成暴力检索

判断是否过度调节只有一条:固定查询集上召回不再涨、延迟继续涨,就是过了拐点。

4. 重建的成本结构

阶段 占比量级 决定因素 能否并行
嵌入计算 大头 文档数、chunk 数、模型吞吐 可,按批水平扩展
索引构建 次之 M、efConstruction、向量维度 部分,受单机内存限制
数据切换 很小 别名切换或路由改配置 秒级

所以"重建慢"通常不是向量库慢,而是嵌入算不过来:先做嵌入缓存(按内容 hash 复用),再谈索引参数。

# 重建三步:建新集合 → 回填 → 切别名(旧集合留只读)
curl -X PUT "$VDB/collections/kb_v7" -d @schema.json
python backfill.py --dst kb_v7 --batch 256 --concurrency 8
curl -X POST "$VDB/aliases" -d '{"alias":"kb","collection":"kb_v7"}'
# 回滚只需把别名指回 kb_v6,不需要重新嵌入

旧集合的成本是内存,但它是分钟级回滚的唯一手段;内存不够时至少留快照文件。

5. 增量与重建的三种策略

策略 做法 残留问题 适用
删旧插新 按文档 id 删除旧 chunk,写入新 chunk 删除是标记,墓碑累积,图连通性变差 更新频繁、量中等
软删标记 写入新版本,查询时按 valid_to 过滤 索引持续膨胀,过滤开销转移给查询 需要审计与回溯
按版本双写 新旧集合并存,按版本路由读 双份内存与双份嵌入成本 模型或切分策略升级
-- 删旧插新必须带版本,避免"删掉了刚写进去的新块"
DELETE FROM chunks WHERE doc_id = ? AND version < ?;
INSERT INTO chunks (doc_id, version, chunk_no, vector, valid_from)
VALUES (?, ?, ?, ?, now());

三种策略的共同残留是墓碑:删除标记不释放内存,图里还留着被删节点的边。判据——墓碑比例(删除数 / 总向量数)超过两成就重建,别再打补丁。

6. 一致性窗口怎么定

窗口 用户体验 实现代价 适用
秒级 改完立刻搜到 要求同步写入 + 强一致读 协作编辑、客服知识库
分钟级 短暂搜到旧内容,可接受 异步管道 + 幂等回填 大多数内部知识库
小时/天级 明确告知"次日生效" 批量重建 文档站、法规库

窗口不是技术指标,是产品承诺。同时要定"看到旧内容时怎么办":标注版本或更新时间,比把窗口压到零便宜得多。

7. 嵌入模型升级:三选一

方案 成本 风险 适用
全量重算 一次嵌入全量 + 一次重建 升级窗口内不可用(除非有旧集合) 数据量小、可停机
双索引并行 双份内存 + 双份写入 路由复杂,容易读到半成品 在线服务、必须可回滚
按桶混用 只重算部分桶 新旧向量不可比,同一查询跨桶排序失效 几乎不建议

硬约束:不同模型的向量空间不可混用。余弦相似度跨两个嵌入空间没有意义,"按桶混用"只在查询不跨桶时成立——而真实查询几乎总是跨桶。

8. 分片与副本在向量库里的意义

维度 ES 分片 向量库分片
目的 水平扩容与并行检索 突破单机内存上限
查询放大 每分片取 topK 后归并 同左,但归并要跨网络传输向量或 id
副本作用 读吞吐 + 高可用 主要是高可用,读扩展收益有限

分片不是免费吞吐:每次查询打到所有分片再归并,topK × 分片数 的中间结果都要传回。正确动机是内存放不下,不是"想更快";副本的价值在故障切换,不在查询性能。

9. 内存占用估算

原始向量:维度 × 数量 × 每维字节数
  fp32: 768 × 3,000,000 × 4B  ≈ 9.2 GB
  fp16: 同上 × 2B          ≈ 4.6 GB
  int8: 同上 × 1B          ≈ 2.3 GB
索引结构(HNSW 经验口径):约 数量 × M × 2 × 4B
  3,000,000 × 32 × 2 × 4B ≈ 0.77 GB
总量 ≈ 原始向量 + 索引结构 + 段与墓碑余量(+10%~30%)

瓶颈在"数量"而非"维度":条数翻倍内存翻倍,而 chunk 切分粒度直接决定条数。

10. 召回率回归检测

{
  "query_set": "eval/queries_v3.jsonl",
  "items": [
    {"q": "订单超时未支付怎么处理", "expect": ["doc-3312#c4", "doc-1180#c2"], "k": 10},
  ],
  "gate": {"min_recall_at_k": 0.9, "max_regression_pp": 2.0}
}

固定查询集 + 期望文档集是唯一能进 CI 的判据,且要写成相对退化(较上一版掉不超过 2 个百分点)而非绝对召回率——绝对口径跨版本不可比。查询集要冻结版本,否则历史对比失效。

11. 灰度切换索引

# 按流量比例切读,两套索引并行一段时间
route:
  read:
    - {collection: kb_v6, weight: 0.9}
    - {collection: kb_v7, weight: 0.1}
  fallback_on_empty: kb_v6      # 新集合召回为空时回退,避免"搜不到"
  compare_metrics: [recall_at_10, p99_latency, empty_result_rate]

最容易漏的是 empty_result_rate:新索引建漏一批文档时,表现不是"变差"而是"部分查询直接空",它比平均召回更早报警。

12. 备份与恢复:能重算 vs 必须备份

资产 能否重算 备份策略
原始文本与元数据 不能,必须持久化 常规数据库备份
嵌入向量 能,但成本 = 全量重算 有嵌入缓存则不必单独备份
索引结构(HNSW 图) 能,成本 = 一次重建 通常不备份,重建替代

判据是重算成本 vs 备份成本:向量能重算,所以多数团队不备份索引本体;但重算耗时即恢复时长,要六小时的就得备份。

13. 排错

现象 原因 处理
文档已更新,检索仍是旧内容 写入进了库但没进索引 / 读的是旧别名 核对索引内条数与 indexed_at
更新后检索变差 墓碑累积,图连通性退化 算墓碑比例,超两成重建
召回突然下降 换了嵌入模型或归一化方式 比对向量范数,确认是否同一空间
内存持续上涨不回落 软删数据未物理清理 关掉纯软删,改删旧插新 + 定期重建
查询 P99 抖动 efSearch 过大或分片过多导致归并放大 降 efSearch,减少跨分片中间结果

14. 边界:暴力检索反而更省心

场景 做法 理由
向量数在十万级以下 Flat 暴力检索 全扫的延迟可接受,精度最高,无参数可调错
更新极少(季度级) 全量重建代替增量 增量的墓碑治理成本高于重建
精度要求极高(合规检索) 精确检索 + 后置重排 近似索引的召回损失不可接受
只有过滤条件命中少数文档 先过滤再向量比较 候选集小,索引结构无用

最后一条常被忽略:过滤砍掉的候选,比索引加速的部分更多。租户、时间、类型这类高选择性过滤先把候选压到几千条,再谈用什么索引。


15. 自测

  1. 文档更新后检索仍是旧内容,三段延迟里最常卡在哪一段?
  2. 向量索引为什么不能像倒排索引那样"边写边搜"?
  3. efSearch 调到多大就算过度调节,判断依据是什么?
  4. 重建的成本大头通常在哪一步,优化顺序应该怎么排?
  5. 墓碑比例到什么程度该重建而不是继续增量?
  6. 什么规模下暴力检索比 HNSW 更合适?
S20-A08 · 向量索引运维:重建、增量与一致性
https://www.clxhxhhr.top/posts/6186/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。