我把向量数据库从 Milvus 切到 pgvector 后,检索 P99 从 230ms 压到 18ms:这 4 个取舍要注意
我把向量数据库从 Milvus 切到 pgvector 后检索 P99 从 230ms 压到 18ms这 4 个取舍要注意RAG 上线三个月后检索 P99 突然从 80ms 涨到 230ms。排查了一圈问题不在 embedding 模型也不在向量维度而是每次查询都要跨一次网络去 Milvus。我们的主服务连着 PostgreSQL向量库却单独跑在一套 K8s 里中间隔着 DNS、负载均衡、序列化反序列化延迟想低都难。后来我把向量检索整体迁到了 pgvector和业务库共用一个 PostgreSQL。改造完后 P99 压到 18ms架构也清爽了不少。但这不是无脑替换有几个取舍必须提前想清楚。老架构Milvus 确实强但对我们太重最开始选型 Milvus 是合理的。它专为向量设计支持十亿级规模、分布式、GPU 索引文档也成熟。我们的场景是把 100 多万条文档切片向量化供 RAG 检索。架构是应用服务 → PostgreSQL业务数据 → Milvus向量检索问题慢慢暴露多一套基础设施Milvus 需要独立集群、独立监控、独立备份。跨网络查询每次 RAG 检索要先去 Milvus 拿 top-k再回 PostgreSQL 查业务字段。运维成本高一次 Milvus 升级导致部分 collection 不可见排查了两个小时。资源利用率低我们的向量量级其实远没触达 Milvus 的优势区间。我算了笔账我们日均向量查询大概 50 万次数据量不到 200 万条向量维度 768。Milvus 的猛兽级能力我们只用到了它 10% 的功能。为这 10% 的功能多养一套集群性价比不高。切到 pgvector把向量存回业务库pgvector 是 PostgreSQL 的向量扩展。安装就是一句CREATE EXTENSION vector;然后表结构可以长这样CREATEEXTENSIONIFNOTEXISTSvector;CREATETABLEdocument_chunks(id BIGSERIALPRIMARYKEY,doc_idBIGINTNOTNULL,chunk_indexINTNOTNULL,contentTEXT,embedding VECTOR(768));CREATEINDEXONdocument_chunksUSINGhnsw(embedding vector_cosine_ops)WITH(m16,ef_construction64);把向量字段和业务字段放在同一张表里最直接的好处是检索结果自带业务字段不需要跨库 JOIN。一个 SQL 就能把向量和业务上下文一起带出来。查询语句SELECTdoc_id,chunk_index,content,embedding:query_vecASdistanceFROMdocument_chunksWHEREdoc_idIN(SELECTdoc_idFROMuser_docsWHEREuser_id:uid)ORDERBYembedding:query_vecLIMIT10;这里WHERE doc_id IN (...)是 RAG 里很常见的业务过滤。在 Milvus 里这种过滤要通过标量过滤或表达式复杂一点就报错在 PostgreSQL 里直接写 SQL爽多了。压测方法不能只盯平均延迟切之前我做了两轮压测。第一轮只看平均延迟Milvus 和 pgvector 差距不大差点让我放弃。后来发现真正的痛点是长尾。Milvus 的 P99 在业务过滤复杂时会突然跳到 200ms 以上因为标量过滤和向量检索不是同一套执行路径协调层会把请求拆成多段。pgvector 走的是 PostgreSQL 自己的执行器计划和索引可以统一优化。压测脚本我用的是 Python asyncio并发 100 个请求每个请求随机抽 100 条向量做 top-10 检索importasyncio,time,random,psycopgfromcollectionsimportdeque DSNpostgresql://user:passlocalhost/dblatenciesdeque()asyncdefquery_one(pool,qvec):t0time.perf_counter()asyncwithpool.connection()asconn:asyncwithconn.cursor()ascur:awaitcur.execute(SELECT doc_id, content FROM document_chunks ORDER BY embedding %s LIMIT 10,(qvec,))awaitcur.fetchall()latencies.append((time.perf_counter()-t0)*1000)asyncdefmain():poolpsycopg.AsyncConnectionPool(DSN,min_size5,max_size20)qvecs[...]# 100 条查询向量awaitasyncio.gather(*[query_one(pool,random.choice(qvecs))for_inrange(1000)])sorted_latsorted(latencies)print(fP50{sorted_lat[500]:.1f}ms P99{sorted_lat[990]:.1f}ms)asyncio.run(main())注意压测时要把 PostgreSQL 连接池和客户端连接池分开。pgvector 的 HNSW 查询主要是 CPU 计算连接池太小会把 PG 的并行度压死。性能对比P99 230ms → 18ms迁移前后的数据指标Milvus 方案pgvector 方案备注平均检索延迟45ms6ms同机房100 并发P99 检索延迟230ms18ms含业务过滤网络往返2 次服务→Milvus→PG1 次同库索引构建时间约 15 分钟约 8 分钟200 万条HNSW运维组件Milvus Etcd MinIOPostgreSQL 本身组件少很多内存占用约 16 GB约 19 GBHNSW 索引在 PG shared_buffers 外P99 的提升主要是消除了跨服务的网络抖动。业务过滤在 PostgreSQL 里走 B 树比在 Milvus 里做标量过滤更稳定。内存虽然多了 3 GB但省掉一套独立集群的 overhead整体成本反而更低。4 个取舍不是银弹1. 索引类型HNSW 还是 IVFFlatpgvector 支持 IVFFlat 和 HNSW。IVFFlat 省内存但高维数据召回和延迟都不如 HNSWHNSW 查询快、召回高但构建慢、内存占用大。我们测过IVFFlatlists100P99 约 55ms召回 92%HNSWm16, ef64P99 约 18ms召回 97%最后选了 HNSW。内存多用了 20%但延迟和召回都更稳。如果你的数据量小、查询频率低IVFFlat 也能用。关键参数是m每个节点最大出度和ef_construction构建时搜索宽度。维度 768 的话m16和ef_construction64是常见起点数据量超过 500 万可以适当调大。2. 和业务库共存资源会打架吗最担心的问题是向量检索把 PostgreSQL 的 CPU/IO 吃光影响业务事务。我们的做法单独表空间把document_chunks放在独立的 SSD 表空间避免和事务表争 IOPS。连接池隔离RAG 查询走只读副本写入走主库。autovacuum 调优大表频繁更新时autovacuum 会抢锁。我们加了autovacuum_vacuum_scale_factor 0.02。ALTERTABLEdocument_chunksSET(autovacuum_vacuum_scale_factor0.02);另外HNSW 索引在查询时几乎不锁表但CREATE INDEX会长时间加锁。上线时可以用CREATE INDEX CONCURRENTLY避免阻塞业务。3. 向量更新批量写入还是实时更新pgvector 的 HNSW 索引在更新时不是完全无锁的。高频单条INSERT或UPDATE会导致索引页竞争写入延迟飙升。我们的做法是业务端先把向量变更写到 staging 表每分钟批量INSERT到主表并用pg_advisory_lock串行化索引更新。importpsycopg2defbatch_insert_embeddings(rows):withpsycopg2.connect(DSN)asconn:withconn.cursor()ascur:cur.execute(SELECT pg_advisory_lock(42))cur.executemany(INSERT INTO document_chunks (doc_id, chunk_index, content, embedding) VALUES (%s, %s, %s, %s),rows)cur.execute(SELECT pg_advisory_unlock(42))conn.commit()实际写入峰值从单条 200ms 降到了批量 15ms。如果更新频率不高也可以直接单条写但要把事务控制在合理的并发数内。4. 扩展天花板什么时候该回退pgvector 不是替代 Milvus 的万能药。如果未来向量规模涨到几千万、几亿或者需要多租户隔离、混合查询极其复杂我们可能还要重新考虑专用向量数据库。目前定的阈值是数据量 500 万条pgvector 够用。日均查询 100 万pgvector 能扛。超过任一阈值评估重新拆分出专用向量库。这里的数据量不是纯向量数而是“活跃索引中的向量数”。如果旧数据可以归档pgvector 的寿命会更长。监控看板我们盯了哪些指标切过去后我搭了一个简单的监控看板核心指标就三个pg_stat_user_tables.n_tup_ins和n_tup_upd观察向量写入速率防止单条更新把索引拖垮。pg_stat_activity中state active的查询数确认连接池没有被打满。业务层的 P99 检索延迟用 Prometheus histogram 直接埋点比看数据库平均延迟更真实。SELECTschemaname,relname,n_tup_ins,n_tup_upd,n_live_tupFROMpg_stat_user_tablesWHERErelnamedocument_chunks;这几个指标够用不用整太复杂。迁移踩坑记录1.pgvector扩展版本问题旧版 PostgreSQL 13 的 pgvector 版本太低不支持 HNSW。我们先升级到了 PostgreSQL 15再装扩展。2. 距离函数选错我们默认用了-L2但 embedding 模型用的是 cosine 归一化应该用。换到 cosine 后召回率提升了 4%。3. 索引参数没调默认 HNSW 参数m16, ef_construction64对 768 维数据够用但ef查询参数在 SQL 里设置SET hnsw.ef 32对 P99 影响很大。调到 64 后召回和延迟达到我们想要的平衡点。SEThnsw.ef64;4. 并发写入时索引膨胀一开始用多线程同时写结果表和索引都膨胀得厉害。改成单连接批量 pg_advisory_lock后稳定下来。写在最后把 Milvus 切到 pgvector不是因为它更好而是因为它更适合我们当前的数据规模和团队栈。省掉一套独立中间件意味着少一套监控、少一份 on-call 压力、少一堆跨服务问题。代价是你要接受 PostgreSQL 的资源边界并学会在 HNSW 索引、批量写入、查询参数之间做权衡。如果你的向量量级和我差不多主库已经是 PostgreSQL不妨先试试 pgvector。别急着上猛兽合适比先进更重要。

相关新闻

systemd-docker 实战案例:构建生产级 Nginx 服务单元配置的完整指南

systemd-docker 实战案例:构建生产级 Nginx 服务单元配置的完整指南

systemd-docker 实战案例:构建生产级 Nginx 服务单元配置的完整指南 【免费下载链接】systemd-docker Wrapper for "docker run" to handle systemd quirks 项目地址: https://gitcode.com/gh_mirrors/sy/systemd-docker 在现代化的 Linux 系统管理…

2026/9/20 14:29:20 阅读更多 →
小程序毕设项目:基于小程序的校园餐饮消费服务系统 智慧高校食堂点餐配送综合平台 (源码+文档,讲解、调试运行,定制等)

小程序毕设项目:基于小程序的校园餐饮消费服务系统 智慧高校食堂点餐配送综合平台 (源码+文档,讲解、调试运行,定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/18 18:29:26 阅读更多 →
小程序毕设项目:基于 SpringBoot + 微信小程序的校园借书驿站服务小程序的设计与实现 校园图书共享借阅驿站小程序的设计与实现 (源码+文档,讲解、调试运行,定制等)

小程序毕设项目:基于 SpringBoot + 微信小程序的校园借书驿站服务小程序的设计与实现 校园图书共享借阅驿站小程序的设计与实现 (源码+文档,讲解、调试运行,定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/14 12:00:09 阅读更多 →

最新新闻

大整数加法速查手册:拆解源码彻底搞定

大整数加法速查手册:拆解源码彻底搞定

大整数加法速查手册:拆解源码彻底搞定 看了一堆教程还是不会写项目?别慌,很多人卡在“看懂了逻辑”和“能独立实现”之间的鸿沟。大整数加法看似简单,实则是考察字符串处理、数组操作及边界条件的经典入门题。本文不玩虚的,直接通过一份…

2026/9/22 3:58:21 阅读更多 →
qvod视频搜索实战项目踩坑:API全变后的3个致命错误

qvod视频搜索实战项目踩坑:API全变后的3个致命错误

qvod视频搜索实战项目踩坑:API全变后的3个致命错误 qvod视频搜索接口在2023年Q4版本升级后,底层数据结构彻底重构,导致大量基于旧版API开发的实战项目直接报错。很多开发者盯着控制台里满屏的 JSON Parse Error…

2026/9/22 3:58:21 阅读更多 →
3个维度图解原理:你x我xx选型避坑指南

3个维度图解原理:你x我xx选型避坑指南

3个维度图解原理:你x我xx选型避坑指南 看了一堆教程还是不会写项目?别怪自己笨,是你没搞懂底层逻辑。 很多人卡在“为什么我的代码跑不通”或者“这个库到底怎么选”上。其实, 你x我xx 的核心不在表面 API,而在其背后的 图解原理 。…

2026/9/22 3:58:21 阅读更多 →
当当网上书店首页复刻踩坑实录与源码解析

当当网上书店首页复刻踩坑实录与源码解析

当当网上书店首页复刻踩坑实录与源码解析 复制来的代码跑不通不知道怎么调,这是很多前端转岗或者练手项目时最崩溃的时刻。你从网上搜到一份“当当网上书店首页”的高仿代码,满怀期待地粘贴进项目,结果页面要么白屏,要么布局错乱,控制台报错一片红。别急…

2026/9/22 3:58:21 阅读更多 →
面试总被问原理?3个方案对比s200spx手写实现完整示例

面试总被问原理?3个方案对比s200spx手写实现完整示例

面试总被问原理?3个方案对比s200spx手写实现完整示例 面试官盯着你,眼神里带着“这你都不知道?”的轻蔑。你脑子一片空白,明明背过八股文,可一涉及底层逻辑就卡壳。这种“原理答不上来”的窘境,是无数转岗开发者的噩梦。别慌,今天不整虚的,直…

2026/9/22 3:57:21 阅读更多 →
3步搞定质量体系图解原理,拒绝Stack Trace报错

3步搞定质量体系图解原理,拒绝Stack Trace报错

3步搞定质量体系图解原理,拒绝Stack Trace报错 面对满屏红色的 Stack Trace,你是不是觉得像看天书?明明代码逻辑没变,一跑就崩,日志里全是 NullPointerException 或者…

2026/9/22 3:57:21 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →