很多人接完 embedding(嵌入)接口、把向量写进库,就以为 RAG(检索增强生成)成了。真跑起来才发现:数据一多查询变慢,召回忽好忽坏,参数改完不知道有没有变好,线上偶尔超时还没有日志可查。这篇不重复向量和索引的概念,只给工程视角的判断标准、参数清单和排查顺序,让你把向量检索这一层做成能上线、能观测、能回归的模块。
很多人接完嵌入接口、把向量写进库,就以为检索这层成了。真跑起来才发现三个问题叠在一起:数据一多查询变慢,召回忽好忽坏,参数改完也不知道到底有没有变好。线上偶尔超时,翻日志还找不到是查询慢还是连接慢。
这篇不讲向量是什么、索引是什么概念,只讲工程落地:索引按什么分档选、参数按什么顺序调、召回怎么留回归集、运行时超时并发降级日志配哪几项。整套做法只有一句话,让向量检索成为能上线、能观测、能回滚的一层,而不是一个改不动也说不清的黑盒。
索引选型:先按数据量和延迟要求分档
选索引之前先量三个数:向量总条数、向量维度、单次查询能容忍的延迟。这三个数决定了你该走哪一档。数据量小的时候,暴力遍历往往比建索引更省事,延迟也不难看;条数上去以后,索引才真正开始有价值。顺序别反。到 /stacks/vector-dbs 可以对照各家产品支持的索引类型和写入特性。
| 场景特征 | 优先考虑 | 主要代价 |
|---|---|---|
| 条数少、维度不高、查询频率低 | 暴力检索,先不建索引 | 条数涨上来后延迟线性上升 |
| 查询延迟敏感、数据变动不频繁 | 图类索引 | 建索引慢、内存占用偏高 |
| 写入频繁、内存预算紧 | 聚类倒排类索引 | 召回略打折,要靠多探簇补 |
| 数据日更、要求边写边查 | 支持增量写入的索引 | 部分实现需要重建,要提前确认 |
把「索引建好」当成第一版而不是终点。上线后拿真实查询分布回看参数,比一开始纠结选哪种索引有效得多。有两类情况不适合照上面的档走:一是查询量极低、几天才查一次的内部工具,直接暴力遍历就行;二是向量条数很少但单条维度极高,内存和计算压力可能比条数更早成为瓶颈。
- 先量条数、维度、可容忍延迟三个数,再定索引类型,不要凭感觉选
- 写入频繁的项目单独查一遍:该索引是每次新增都重建,还是支持边写边查
- 把索引当作第一版,上线后按真实查询分布再回看参数
- 数据量小、查询频率低的场景直接用暴力检索,别为了建索引而建索引
参数调优:每次只动一个,动完必须回测
向量检索的参数看着多,其实可以分两组:一组影响召回,比如搜多少候选、探多少个簇;一组影响性能和写入,比如分片、副本、量化压缩。这两组一定要分开调,同时改就说不清是谁在起作用。建议顺序是先把召回质量固定达标,再压延迟,最后才考虑省内存。反过来做很容易先把召回压坏,再花时间回头救。
- 分组先把手头所有参数归到召回组或性能组,写在一张纸上,避免调着调着混在一起。
- 固定召回只动召回组参数,跑到小评测集上的命中结果稳定达标为止,记录当时的配置。
- 再压延迟召回达标后,再动分片、量化这类性能参数,跑同一套评测集对比延迟变化,确认召回没有掉下去。
- 最后省内存内存吃紧时再考虑压缩类参数,压完必须重跑评测集,别默认压缩对召回没影响。
- 留记录每次改动记三件事:改了什么、在哪个数据集上、召回和延迟各变成多少。没有这条记录,下次出问题只能靠猜。
召回验证:留 20 条真实问题当回归集
召回没法靠感觉判断,得有一个固定的小评测集。做法是从真实业务里挑二十条左右的问题,人工标出每道题的正确答案应该来自哪几段内容,固化下来。之后每次改索引、换嵌入模型、动切片策略,都拿它跑一遍。这个集合不用大,关键是要真实、要固定、要能重复跑。
| 指标 | 看什么 | 不适用的情况 |
|---|---|---|
| 命中率 | 正确片段有没有被捞回来 | 只有一条标准答案、业务本身就不确定时参考价值有限 |
| 命中位置 | 正确片段排在前几还是垫底 | 对话式检索里位置要求可以放宽 |
| 噪声比例 | 捞回的不相关片段会不会把上下文挤爆 | 短上下文场景噪声容忍度更低,需要单独看 |
| 改动风险等级 | 换嵌入模型属高风险,换索引参数属中低风险 | 任何改动都不能只看单次结果就下结论 |
换嵌入模型属于高风险改动,必须走完整回测;只调索引参数可以只跑子集,但一定要有基线可比。检索质量下滑时,排查顺序是先怀疑进料,也就是切片、清洗、去重,再怀疑索引本身。多数召回问题其实是切片切坏了。切片与清洗要点可以到 /guides/rag-kb-build-pipeline 和 /guides/pdf-ocr-doc-cleaning 对照流程。噪声把上下文挤爆的问题,参考 /guides/context-budget-engineering 的做法。
运行时:超时、并发、降级与日志四件事
前面三节决定检索准不准,这一节决定它稳不稳。超时要按层设:连接超时、查询超时、整体接口超时分开配,别只给一个总数。向量查询慢的时候,要能一眼看出是连接层拖的还是查询层拖的。分层设置的思路可以到 /guides/api-timeout-tuning 对照。
并发要跟写入错开。批量灌数据和线上查询抢资源是很常见的卡点,灌库放在低峰、限制批大小,配合排队和失败重投。这套清单可以到 /guides/batch-concurrency-throttle-guide 看。降级路径要提前定好三条:向量库挂了或超时,是退回全文检索、退回缓存、还是直接少给几条结果,每种都要有明确的触发条件。混合检索的选型对照在 /guides/vector-fulltext-hybrid-search。
- 日志至少记:查询向量来源、候选数量、最终命中条数、耗时、走了哪条降级路径
- 没有这几项,线上「搜不到」的投诉基本没法复盘,只能靠猜
- 降级触发条件写死在配置里,不要等出事现场再决定
- 超时按层配,连接、查询、接口分开,方便定位是哪一层慢
常见问题
向量数据库一定要单独部署一套吗?
不一定。向量条数在几千到几万、查询不频繁时,用普通数据库的向量扩展甚至内存里算都能撑住。判断标准是数据量大不大、查询 QPS(每秒查询数)高不高、能不能接受把检索和业务库绑在一起。规模上来或需要独立扩容时再拆,具体产品到 /stacks/vector-dbs 看目录。
检索结果不稳定,一会儿准一会儿不准,从哪查?
按这个顺序:先看进料有没有重复和脏数据,再看切片是不是把一段完整意思切断了,然后确认查询时用的嵌入模型和入库时是不是同一个,最后才是索引参数。前三条占大多数。如果只是偶发,重点看是不是超时导致候选变少,或者并发高峰时被限流降级了。
怎么判断一次索引或模型改动到底有没有变好?
留一个固定的小回归集,改动前后各跑一遍,比召回位置和噪声比例,不要凭感觉。只跑一两条问题容易得出相反结论。如果改动同时动了嵌入模型和切片,建议分两次上,否则出了问题没法定位是哪一步导致的。