pg_vectorsearch 火了,但 RAG 先补上 pgvector 的 3 个坑 这两天 Hacker News 前排被 Timescale 新发的 PostgreSQL 向量检索扩展 pg_vectorsearch 占了一块。它主打一个 PRISM 索引官方说在 1 亿向量上建索引只要 49 分钟而 pgvector 的 HNSW 要 42 小时查询延迟还能砍一半。数字很炸但发布帖写得很清楚这是 alpha要求 PostgreSQL 18只能测试、不能上生产。对绝大多数还在跑 pgvector 0.5/0.6、PostgreSQL 14~16 的 RAG 服务来说眼前能落地的不是换引擎而是先把 pgvector 用对。下面三个坑是调向量检索延迟时最高频的问题。热点背景PRISM 到底快在哪pg_vectorsearch 是 Timescale原 TimescaleDB 团队在 2026-10-13 开源的 PostgreSQL 扩展状态明确标着 alpha。它新增的索引访问方法叫 PRISMPartitioned Routing Index for Similarity Matching。结构上是 IVF 风格用一棵层次质心树把向量分区查询时只路由到最近的几个簇向量用 RaBitQ 量化压缩成约每维 1 bit并带理论误差界先用压缩码粗筛、再用全精度向量重排距离计算走 SIMDAVX2 / AVX512 / NEON。官方自测里最抢眼的一组数来源 Timescale 发布帖厂商口径需打折扣看同一台 64 vCPU、512 GiB 的机器1 亿条 Cohere 向量HNSW 建索引 42 小时、索引占 180 GBPRISM 建索引 49 分钟、索引占 25 GBp50 查询延迟约为 HNSW 的一半。它还能直接建在已有的 pgvector 列上——vec32/vec16 与 vector/halfvec 二进制兼容不改表、不改查询。代价是必须 PostgreSQL 18 以上且目前只是 alpha跨预发版不保证能平滑升级。换句话说值得盯着但现在别急着动生产库。先把手里已经跑着的 pgvector 调明白性价比最高。索引类型建索引特点内存占用现阶段适用IVFFlat单遍扫描、建时锁表低小表 / 只读分析HNSW快、不阻塞写入高大表 / 高频写入PRISMalpha官方自测 49min/1 亿低未来生产候选当前仅测试环境准备想复现下面的坑最快是起一个带 pgvector 的 PostgreSQL 容器再用 Python 连上去跑 EXPLAIN 看执行计划docker run -d --name pg -e POSTGRES_PASSWORDpostgres -p 5432:5432 ankane/pgvector:pg17 pip install pgvector[psycopg] numpy坑一忘了 SET ivfflat_probes索引等于白建pgvector 的 IVFFlat 索引靠ivfflat_probes控制查询时扫描的聚类数默认值只有 1。也就是你建了索引、查询也走 Index Scan但每次只翻 1 个 list召回率会悄悄掉到很低你还以为索引生效了。下面这段脚本在同一张表上对比 probes1 和 probes20 的执行计划跑出来你会看到 probes1 时只扫了 1 个 listimport os import numpy as np import psycopg from pgvector.psycopg import register_vector DSN os.getenv(PG_DSN, postgresql://postgres:postgreslocalhost:5432/postgres) conn psycopg.connect(DSN) register_vector(conn) conn.execute(CREATE EXTENSION IF NOT EXISTS vector) conn.execute(DROP TABLE IF EXISTS docs) conn.execute(CREATE TABLE docs (id int PRIMARY KEY, emb vector(128))) rows [(i, np.random.randn(128).astype(float32)) for i in range(5000)] with conn.cursor() as cur: cur.executemany(INSERT INTO docs (id, emb) VALUES (%s, %s), rows) conn.commit() conn.execute(CREATE INDEX ON docs USING ivfflat (emb vector_l2_ops) WITH (lists 70)) # lists ≈ sqrt(行数) q np.random.randn(128).astype(float32) def plan(probes): conn.execute(fSET ivfflat_probes {probes}) out conn.execute( EXPLAIN ANALYZE SELECT id FROM docs ORDER BY emb - %s LIMIT 10, [q] ).fetchall() return \n.join(r[0] for r in out) print( probes1默认) print(plan(1)) print(\n probes20 ) print(plan(20)) conn.close()跑出来会看到probes1 时是Index Scan但只扫了 1 个 list把 probes 调到 20扫描的 list 数上来召回才接近暴力检索。生产环境一般把ivfflat_probes设成 lists 的几分之一到全部再按 EXPLAIN 输出里的Lists行确认。坑二表太小或 lists 配错planner 直接走 Seq ScanIVFFlat 不是银弹。表行数太少pgvector 经验上低于约 1000 行或者lists设得离谱优化器会认为顺序扫描更便宜于是你辛辛苦苦建的索引根本不被用。一个常见反模式是给小表也硬上 IVFFlat还纳闷为什么延迟没降。lists的经验起点是sqrt(行数)数据量涨了要重建索引才能改 lists。更麻烦的是 IVFFlat 建索引是单遍、会拿表锁大数据量上线期间建索引会阻塞写入。这也是为什么 pgvector 0.5 之后官方更推荐USING hnswHNSW 建索引快、支持并行、不阻塞写入代价是内存占用更高。简单说小表 / 只读分析用 IVFFlat 够用大表高频写入优先 HNSWCREATE INDEX ON docs USING hnsw (emb vector_l2_ops);坑三cosine 距离没归一化召回率偷偷掉用余弦相似度时pgvector 的vector_cosine_ops并不会替你把向量归一化。如果原始向量模长不齐cosine 实际上会被模长偏置召回结果和真正语义最近对不上。插入前先归一化是最稳的做法def normalize(v): n np.linalg.norm(v) return v / n if n 0 else v emb normalize(np.random.randn(128).astype(float32)) conn.execute(INSERT INTO docs (id, emb) VALUES (%s, %s), [1, emb])L2 距离vector_l2_ops没有这个问题但很多 RAG 场景用的就是 cosine这条最容易漏。顺带提一句上面看到的 PRISM 也提供vec32_cosine_ops它同样依赖你喂进去的是归一化向量才能拿到发布帖里那档召回率-- 需要 PostgreSQL 18 且安装 alpha 版 pg_vectorsearch CREATE EXTENSION pg_vectorsearch; CREATE INDEX documents_embedding_prism ON documents USING prism (embedding vec32_cosine_ops); SET prism.nprobe 40; -- 0 自动约 0.95 召回 SELECT * FROM documents ORDER BY embedding [3,1,2] LIMIT 5;小结pg_vectorsearch 的 PRISM 把Postgres 里做亿级向量检索的门槛又往下拉了一截49 分钟建 1 亿向量确实亮眼但 alpha PG18 的门槛意味着它短期进不了生产。对正在跑 pgvector 的服务先把ivfflat_probes、索引类型选型、向量归一化这三件事核对一遍往往能直接把查询延迟和召回率拉回预期比急着换引擎实在。等它脱离 alpha、PG18 普及再评估迁移也不迟。