暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

1000万向量下RAG从5ms到238ms,pgvector与专用向量库实测对比

原创 底层玩家老张 5小时前
2

各位,我是老张。今天从一次实战出发,往下拆一层。

上周四晚上九点,客服问答群的告警消息开始刷屏。P99延迟从5ms涨到238ms,召回率从92%掉到78%。用户问“发票怎么开”,系统回一段“会员积分规则”。这系统是RAG,上个月刚上生产,demo阶段跑得飞快。10万向量时查询秒回,演示效果惊艳,领导当场拍板上线。全量导完1000万条客服文档,问题全冒出来。团队第一反应是换Embedding模型,连夜试了三个,召回纹丝不动。折腾两天,EXPLAIN一跑,问题根本不在模型。

RAG检索链路是embedding、向量库、topK、拼prompt。大多数人优化第一步和最后一步,没人看中间。今天把向量索引这层拆开讲。

先给结论:RAG慢和不准,多半是向量索引和存储架构选错,模型背了锅。向量检索走的是近似最近邻,不是精确查找。B+Tree在这套场景下基本不干活,得靠HNSW或IVF这类专门结构。索引参数和过滤条件怎么配,直接决定线上延迟和召回。

关键点 说明
检索本质 ANN 近似最近邻,牺牲一点精度换速度
B+Tree 失效 高维向量没有顺序,建不了传统索引
HNSW 分层图加跳表思想,快但吃内存
IVF 聚类加倒排,省内存但召回靠参数
混合过滤 先过滤还是先向量,是生产最大的坑

这里有个反直觉的点:向量越多,越吃索引参数。默认值只对demo数据友好,上生产不调,召回率掉给你看。

为什么 B+Tree 不干活

传统数据库的索引是B+Tree,数据按值排序。查“价格等于99”直接二分,查“价格50到100”顺着叶子扫。排序结构对等值和范围查询是降维打击。但向量不一样,一条客服文档转成768维浮点数组,查询是找“最接近”的向量,不是“等于”。高维空间没有天然顺序,所谓接近,就是算欧氏距离或余弦相似度。

精确KNN要全表扫,逐条算距离。1000万条乘768维,一次查询几十亿次浮点运算,线上扛不住。工业界都做ANN,允许漏掉一部分真近邻,换回数量级的速度提升。千万级向量下,召回100%是奢侈品,不是必需品。

HNSW 到底在干嘛

HNSW是分层图结构,思想跟跳表一样。底层有全部节点,往上每层稀疏采样。查询从顶层随机入口开始,每层贪心找最近邻居,沿着边逐层下探。

第3层:A → C
第2层:A → C → E
第1层:A → B → C → D → E

查询路径:
从顶层入口 A 开始,找最近邻居
下到第2层,沿边走到 C
再下到第1层,从 C 出发,走到 E

每层都在缩小范围,最后落在目标附近。图建得好,查询路径就短。图质量看三个参数:M、efConstruction、efSearch。M是每个节点的最大连接数,M越大图越密,召回越高,内存越高;efConstruction是建图候选集,越大建图越久;efSearch是查询候选集,越大召回越高,延迟越高。具体取多少,我也不敢拍胸脯,得拿你自己的数据跑。

CREATE INDEX idx_embedding ON doc_vectors USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 100); -- 查询前设置 ef_search SET hnsw.ef_search = 100;

HNSW的代价在内存。每个向量的邻居指针常驻内存,M取16时,1000万向量光图结构就吃掉几个GB。数据量上来,先算内存预算,再谈性能。

IVF 倒排聚类

IVF思路完全不同。先把全部向量做KMeans聚类,得到nlist个聚类中心。查询时算一下向量离哪些中心近,只进最近的nprobe个桶搜,别的桶不碰。

维度 HNSW IVF
结构 多层图 聚类加倒排列表
查询路径 逐层下探 选桶后桶内扫描
内存 高,邻居常驻 低,存聚类中心
召回 高,参数灵活 靠 nprobe,调参空间小
适合 高并发在线检索 海量数据,内存受限

IVF的召回受nlist和nprobe支配。nlist太大,每个桶向量少,聚得碎;nprobe太小,真近邻落在没进的桶里,召回直接崩。它比HNSW省内存,但参数一调不好,召回率很难看。

索引选好了,但生产环境还有一个更大的坑等着你——过滤条件怎么和向量检索配合。

生产最痛的坑:过滤 + 向量

demo里没人加过滤条件。生产不一样,客服知识库要按业务线、按租户、按时间筛。这就引出生产环境最头疼的问题:先过滤还是先向量。

先过滤再向量,就是先走B+Tree筛出符合条件的记录,再算向量距离。过滤能筛掉大部分数据时,这条路快。但向量索引没法同时挂在过滤列上,数据一多,过滤完再向量,索引基本帮不上忙。先向量再过滤,让向量索引找回topK,再套过滤条件。问题在于topK里可能没几条符合条件,过滤完剩两三条,召回率暴跌。这跟向量算得准不准没关系,是流程设计的问题。

场景 先过滤再向量 先向量再过滤
过滤能筛掉九成 快,召回稳 快但召回可能崩
过滤条件稀疏 全表扫,慢 快,但过滤后剩很少
高并发 有索引可走 索引兜底

我在这上面栽过跟头,避坑清单细说。

压测数据

以下数据来自我自己的测试环境:16核64G、NVMe SSD,1000万条768维向量。具体数值会随硬件变化,但趋势是稳的。

方案 索引 QPS P99 召回率@10 内存
pgvector HNSW M=16, ef_search=100 820 38ms 95.2% 6.1G
pgvector HNSW M=16, ef_search=40 默认 1500 18ms 78.4% 6.1G
pgvector 无索引,暴力扫描 3 320ms 100% -
专用向量库 HNSW M=16, ef_search=100 2400 11ms 95.8% 5.4G

看默认ef_search那行。延迟好看,召回崩到78%。demo数据少,默认值够用;数据一大,召回先撑不住。这就是“demo秒回,生产卡死”的另一个真相——参数没跟上数据量。

同一个pgvector,HNSW参数扫描:

M ef_search P99 召回率@10
8 40 12ms 71%
16 100 38ms 95%
32 200 71ms 98.6%

延迟和召回互相拉扯。想要95%以上召回,就得接受几十毫秒的P99。业务对延迟敏感还是对准确敏感,决定参数往哪调。

压测数据看完了,下面给一套能直接用的决策框架。

架构决策框架

数据量小,百万以内,过滤简单,pgvector默认参数就能打。先把ef_search往上拉,观察召回变化,这是最低成本的优化。数据量千万级,内存充足,上HNSW并调M和ef_search。内存紧张,考虑IVF或专用向量库。纯检索吞吐是硬指标时,专用向量库优势明显。过滤复杂、按租户隔离的业务,先想清楚检索流程。分段索引、按租户分库,比单库硬扛过滤靠谱。

场景 推荐做法
百万以内,简单过滤 pgvector,调 ef_search
千万级,内存充足 HNSW,M=16 起,ef_search 按召回调
千万级,内存紧张 IVF 或专用向量库
强过滤,多租户 分段索引,按租户分片
高吞吐在线检索 专用向量库,向量放内存

踩过的坑

第一个坑,以为建了HNSW就完事。实际查询没走索引,EXPLAIN显示Seq Scan。因为我用了ORDER BY embedding距离排序,但没带LIMIT。pgvector要ORDER BY加LIMIT才走索引,不带limit直接全表算距离。这个坑排了一天。

-- 没带 LIMIT:走全表扫描
EXPLAIN SELECT * FROM doc_vectors
ORDER BY embedding <-> $1;

-- 带 LIMIT:才走 HNSW 索引
EXPLAIN SELECT * FROM doc_vectors
ORDER BY embedding <-> $1 LIMIT 10;

第二个坑,召回率只盯topK平均值。上生产后线上召回78%,我盯着测试指标怎么都对不上。后来发现是混合过滤场景——先向量后过滤,过滤完topK剩两三条。测试时没加过滤条件,指标全是虚的。

第三个坑,多租户塞一个表。十个租户共享一张向量表,按租户过滤,先向量再过滤,每个租户的topK都被稀释。拆成租户独立分区后,召回和延迟一起回来了。这是我搞过的最脏的一次,返工了一周。

结语

RAG的瓶颈不在模型,在数据链路。向量索引选什么、参数怎么调、过滤怎么设计,决定线上是秒回还是卡死。这些是数据库内核那套东西,跟大模型关系不大。

写这篇不是劝你不用向量数据库,是想说把原理搞清楚再选型。B+Tree解决不了的问题,HNSW和IVF怎么解决,各有各的代价。数据量、内存、过滤复杂度,先量清楚再动手。

各位生产上有没有被RAG检索坑过?召回崩还是延迟炸?评论区聊聊,一起对参数。

我是底层玩家老张。咱不聊概念,只聊源码和压测跑出来的东西。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论