mcpField
P
pgvector-semantic-search

pgvector-semantic-search

github.com/timescale数据与数据库1.9k947 次安装6 天前更新
代码仓库

关于 pgvector-semantic-search

面向使用 PostgreSQL 和 pgvector 构建语义搜索的开发者,提供向量存储、索引、查询与调优指南。内容涵盖 HNSW、量化及过滤查询等实践。

主要功能

  1. 01配置 halfvec 与余弦距离
  2. 02创建和调优 HNSW 索引
  3. 03使用二进制量化与重排
  4. 04处理过滤条件下的向量搜索
  5. 05批量加载与索引维护

适用场景

  1. 01为文本嵌入构建语义搜索
  2. 02调优大型向量数据集的检索
  3. 03改善过滤查询的召回率
  4. 04排查向量索引性能问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/timescale/pg-aiguide --skill pgvector-semantic-search

安装后在 Claude Code 中输入/pgvector-semantic-search确认已加载。

详细介绍

pgvector 语义搜索

语义搜索根据含义而非精确关键词查找内容。嵌入模型会将文本转换为高维向量,其中含义相似的内容会映射到彼此相近的点。pgvector 在 PostgreSQL 中存储这些向量,并使用近似最近邻(ANN)索引快速查找最接近的匹配项,无需离开数据库即可扩展到数百万行。将文本与其嵌入一起存储,然后将搜索文本转换为向量进行查询,并返回距离最小的行。

本指南介绍 pgvector 的设置和调优,不涉及嵌入模型选择或文本分块;这两者会显著影响搜索质量。所有功能(halfvec、binary_quantize、迭代扫描)均要求 pgvector 0.8.0+。

推荐默认配置

除非有特定原因,否则请使用此配置。

  • 嵌入列的数据类型:halfvec(N),其中 N 为嵌入维度(所有位置必须一致)。示例使用 1536;请替换为你的维度 N。
  • 距离:余弦(<=>)
  • 索引:HNSW(m = 16,ef_construction = 64)。使用 halfvec_cosine_ops,查询时使用 <=>。
  • 查询时的召回率:SET hnsw.ef_search = 100(根据已发布的基准测试,这是一个不错的起点;提高该值可在延迟增加的情况下提升召回率)
  • 查询模式:ORDER BY embedding <=> $1::halfvec(N) LIMIT k

对于大多数文本嵌入工作负载,此配置能在速度和召回率之间取得良好平衡。

核心规则

  • 在每个数据库中启用扩展:CREATE EXTENSION IF NOT EXISTS vector;
  • 默认使用 HNSW 索引——速度与召回率的平衡更佳,可在空表上创建,无需训练步骤。只有在写入密集型或受内存限制的工作负载中才考虑使用 IVFFlat。
  • 默认使用 halfvec——以 halfvec 存储并建立索引,可将存储和索引大小减少 50%,且召回率损失极小。
  • 初始数据批量加载后再创建索引,以获得最佳构建性能。
  • 在生产环境中并发创建索引:CREATE INDEX CONCURRENTLY ...
  • 默认使用余弦距离(<=>):对于未归一化的嵌入,请使用余弦距离。对于单位归一化的嵌入,余弦距离和内积的排序结果相同;默认使用余弦距离。
  • 查询运算符必须与索引运算符类匹配:使用 halfvec_cosine_ops 建立的索引要求查询中使用 <=>;halfvec_l2_ops 要求使用 <->;运算符不匹配时不会使用索引。
  • 始终显式转换查询向量($1::halfvec(N)),以避免预备语句中的隐式转换失败。
  • 数据和查询始终使用相同的嵌入模型。只有生成向量的模型相同时,相似度搜索才能正常工作。

类型规则

  • 将嵌入存储为 halfvec(N)
  • 将查询向量转换为 halfvec(N)
  • 将二进制量化向量作为 bit(N) 存储在生成列中
  • 不要混用 vector / halfvec / bit,除非显式转换
  • 切勿在 ORDER BY 中对表列调用 binary_quantize();应改为存储其结果
  • 维度必须匹配:halfvec(1536) 列要求查询向量转换为 ::halfvec(1536)。

标准模式

-- Store and index as halfvec
CREATE TABLE items (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  contents TEXT NOT NULL,
  embedding halfvec(1536) NOT NULL  -- NOT NULL requires embeddings generated before insert, not async
);
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

-- Query: returns 10 closest items. $1 is the embedding of your search text.
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;

有关其他距离运算符(L2、内积等),请参阅 pgvector README。

HNSW 索引

推荐使用的索引类型。它会创建多层可导航图,在速度与召回率之间提供更佳平衡。可在空表上创建(无需训练步骤)。

CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

-- With tuning parameters
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops) WITH (m = 16, ef_construction = 64);
HNSW 参数
参数默认值描述
m16每层的最大连接数。数值越高,召回率越好,占用内存越多
ef_construction64构建时的候选列表。数值越高,图质量越好,构建速度越慢
hnsw.ef_search40查询时的候选列表。数值越高,召回率越好,查询速度越慢。应 ≥ LIMIT。

ef_search 调优(粗略指南,实际结果因数据集而异):

ef_search近似召回率相对速度
40较低(部分基准测试中约为 95%)1x(基准)
100较高慢约 2x
200很高慢约 4x
400接近精确慢约 8x
-- Set search parameter for session
SET hnsw.ef_search = 100;

-- Set for single query
BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

IVFFlat 索引(通常不推荐)

默认使用 HNSW。只有当 HNSW 的运维成本比最高召回率更重要时,才使用 IVFFlat。

以下情况可选择 IVFFlat:

  • 写入密集或数据持续变化,且愿意频繁重建索引
  • 经常重建索引,并希望构建时间和内存用量可预测
  • 内存有限,无法让 HNSW 图基本常驻内存
  • 数据经过分区或分层,且此索引位于较冷的分区

如果需要以下特性,请避免使用 IVFFlat:

  • 低延迟下的最高召回率
  • 尽量少的调优
  • “设置后无需再管”的索引

注意:

  • 创建 IVFFlat 索引前,必须先有数据。
  • 召回率取决于 lists 和 ivfflat.probes;probes 越高,召回率越好,查询速度越慢。

入门配置:

CREATE INDEX ON items
USING ivfflat (embedding halfvec_cosine_ops)
WITH (lists = 1000);

SET ivfflat.probes = 10;

量化策略

  • 量化是内存方面的选择,而非召回率方面的选择。
  • 默认使用 halfvec 进行存储和索引。
  • 估算 HNSW 索引占用空间时,可按每个 1536 维 halfvec 约 4–6 KB(m=16)计算(数量级估算);3072 维约为其 2 倍;m=32 时,HNSW 链接和图的开销大致翻倍。
  • 如果 p95/p99 延迟上升,而 CPU 大多处于空闲状态,HNSW 索引可能已无法常驻内存。
  • 如果 halfvec 无法装入内存,请使用二进制量化并重新排序。
1536 维向量指南

m=16、1536 维时 halfvec 的近似容量(假设 RAM 大部分可用于索引缓存):

RAMhalfvec 向量数近似上限
16 GB约 2–3M 个向量
32 GB约 4–6M 个向量
64 GB约 8–12M 个向量
128 GB约 16–25M 个向量

对于 3072 维嵌入,将这些数字除以约 2。
对于 m=32,容量也要除以约 2。

如果此规模下索引无法装入内存,请使用二进制量化。

这些是范围,不是保证值。请在负载下监控缓存驻留情况和 p95/p99 延迟,以验证效果。

二进制量化(适用于超大型数据集)

内存占用减少 32 倍。搭配重新排序使用,可获得可接受的召回率。

-- Table with generated column for binary quantization
CREATE TABLE items (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  contents TEXT NOT NULL,
  embedding halfvec(1536) NOT NULL,
  embedding_bq bit(1536) GENERATED ALWAYS AS (binary_quantize(embedding)::bit(1536)) STORED
);

CREATE INDEX ON items USING hnsw (embedding_bq bit_hamming_ops);

-- Query with re-ranking for better recall
-- ef_search must be >= inner LIMIT to retrieve enough candidates
SET hnsw.ef_search = 800;
WITH q AS (
  SELECT binary_quantize($1::halfvec(1536))::bit(1536) AS qb
)
SELECT *
FROM (
  SELECT i.id, i.contents, i.embedding
  FROM items i, q
  ORDER BY i.embedding_bq <~> q.qb -- computes binary distance using index
  LIMIT 800
) candidates
ORDER BY candidates.embedding <=> $1::halfvec(1536) -- computes halfvec distance (no index), more accurate than binary
LIMIT 10;

80 倍过采样比例(为 10 个结果检索 800 个候选项)是一个合理的起点。二进制量化会损失精度,因此重新排序时需要更多候选项,才能找到真正的最近邻。若召回率不足,可增加候选项;若重新排序延迟过高,可减少候选项。

不同数据集规模下的性能

规模向量数配置备注
小型<100K默认值索引可选,但能改善尾延迟
中型100K–5M默认值监控 p95 延迟;这是最常见的生产环境规模
大型5M+ef_construction=100+内存驻留至关重要
超大型10M+二进制量化 + 重新排序如有可能,优先增加 RAM 或进行分区

首先调整 ef_search 以改善召回率;只有在召回率趋于平稳且内存允许时,才增加 m。在并发负载下,如果索引无法装入内存,尾延迟会飙升。二进制量化是最后的备选方案;优先考虑增加 RAM 或进行分区。

筛选最佳实践

筛选式向量搜索需要谨慎处理。根据筛选条件的选择性和查询形态,筛选可能导致提前终止(行数不足、结果缺失),也可能增加工作量(延迟)。

迭代扫描(筛选条件较严格时推荐)

默认情况下,如果存在 WHERE 子句,HNSW 可能会提前停止,从而导致结果数量少于预期。迭代扫描允许 HNSW 继续搜索,直到找到足够多符合筛选条件的行。

当筛选条件显著缩小结果集时,请启用迭代扫描。

-- Enable iterative scans for filtered queries
SET hnsw.iterative_scan = relaxed_order;

SELECT id, contents
FROM items
WHERE category_id = 123
ORDER BY embedding <=> $1::halfvec(1536)
LIMIT 10;

如果结果仍然较少,请增加扫描预算:

SET hnsw.max_scan_tuples = 50000;

权衡:增大 hnsw.max_scan_tuples 可以提高召回率,但可能会显著增加延迟。

无需迭代扫描的情况:

  • 筛选条件匹配表中的很大一部分数据(选择性低)
  • 通过 B-tree 索引进行预筛选
  • 查询单个分区或部分索引
选择合适的筛选策略

筛选条件选择性高(少于约 10k 行) 在筛选列上使用 B-tree 索引,以便 Postgres 在 ANN 之前进行预筛选。

CREATE INDEX ON items (category_id);

基数低(不同值较少) 针对每个筛选值使用部分 HNSW 索引。

CREATE INDEX ON items
USING hnsw (embedding halfvec_cosine_ops)
WHERE category_id = 11;

筛选值多或数据集较大 按筛选键分区,使每个 ANN 索引保持较小。

CREATE TABLE items (
  embedding halfvec(1536),
  category_id int
) PARTITION BY LIST (category_id);
关键规则
  • 匹配行数较少的筛选条件需要预筛选、分区或迭代扫描。
  • 始终在真实负载下,通过测量 p95/p99 延迟和已访问元组数来验证筛选查询。
替代方案:使用 pgvectorscale 按标签筛选

对于带标签筛选的大型数据集,pgvectorscale 的 StreamingDiskANN 索引支持对 smallint[] 列建立筛选索引。标签与向量一同建立索引,从而无需承担 HNSW 后置筛选的准确率权衡,即可高效执行筛选搜索。有关设置详情,请参阅 pgvectorscale 文档。

批量加载

-- COPY is fastest; binary format is faster but requires proper encoding
-- Text format: '[0.1, 0.2, ...]'
COPY items (contents, embedding) FROM STDIN;
-- Binary format (if your client supports it):
COPY items (contents, embedding) FROM STDIN WITH (FORMAT BINARY);

-- Add indexes AFTER loading
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops);

维护

  • 定期执行 VACUUM:在更新或删除后执行——过期条目可能会一直保留,直到清理完成
  • 执行 REINDEX:如果高频变更后性能下降,可从头重建图
  • 对于频繁删除的写入密集型工作负载,可考虑使用 IVFFlat,或通过 hypertables 按时间分区

监控与调试

-- Check index size
SELECT pg_size_pretty(pg_relation_size('items_embedding_idx'));

-- Debug query performance
EXPLAIN (ANALYZE, BUFFERS) SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;

-- Monitor index build progress
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" 
FROM pg_stat_progress_create_index;

-- Compare approximate vs exact recall
BEGIN;
SET LOCAL enable_indexscan = off;  -- Force exact search
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

-- Force index use for debugging
BEGIN;
SET LOCAL enable_seqscan = off;
SELECT id, contents FROM items ORDER BY embedding <=> $1::halfvec(1536) LIMIT 10;
COMMIT;

常见问题(症状 → 修复方法)

症状可能原因修复方法
查询未使用 ANN 索引缺少 ORDER BY + LIMIT、运算符不匹配或发生隐式类型转换使用与索引操作符类匹配的距离运算符进行 ORDER BY;显式转换查询向量
结果少于预期(筛选查询)HNSW 因筛选条件而提前停止启用迭代扫描;增大 hnsw.max_scan_tuples;或进行预筛选(B-tree)、使用部分索引或分区
结果少于预期(未筛选查询)ANN 召回率过低增大 hnsw.ef_search
CPU 使用率低但延迟高HNSW 索引未驻留在内存中使用 halfvec,减小 m/ef_construction,增加 RAM,进行分区,或使用二进制量化
索引构建缓慢构建内存或并行度不足增大 maintenance_work_mem 和 max_parallel_maintenance_workers;在批量加载后构建
内存不足错误索引过大,超出可用 RAM使用 halfvec,减小索引参数,或改用二进制量化并重新排序
结果为空或缺失向量为 NULL 或零向量避免使用 NULL 嵌入;使用余弦距离时不要使用零向量

评论

支持 Markdown