跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

使用指南

在 pgvecto.rs 中创建索引、执行相似度搜索、监控、量化以及兼容性配置。

1 - 索引

索引是构建高效搜索数据结构的过程。pgvecto.rs 支持三种索引算法:暴力搜索(flat)、IVF 和 HNSW。

索引是构建支持高效搜索的数据结构的过程。pgvecto.rs 支持三种索引算法:暴力搜索(flat)、IVF 和 HNSW。默认算法为 HNSW。

要为向量构建索引,首先创建一张名为 items 的表,其中包含类型为 vector(n) 的 embedding 列,然后向表中填充生成的数据。

CREATE TABLE items (embedding vector(3));
INSERT INTO items (embedding) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
提示

vector 是适用于绝大多数场景的数据类型。pgvecto.rs 还支持其他特定的向量类型,包括:

您可以使用以下 SQL 为平方欧氏距离创建向量索引:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops);

vector_l2_ops 操作符类用于计算平方欧氏距离。有关操作符类的完整列表,请参阅参考文档。

若要将索引算法从默认的 HNSW 切换为 IVF,请执行以下 SQL 命令。更多使用示例请参阅下文的示例。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
$$);

现在您可以使用以下 SQL 执行 KNN 搜索,利用向量索引加速查询:

SELECT * FROM items ORDER BY embedding <-> '[3,2,1]' LIMIT 5;
详情

pgvecto.rs 采用异步方式构建索引。当您向表中插入新行时,数据会首先写入一个仅追加(append-only)文件中。后台线程会定期将新插入的行合并到现有索引中。当用户在合并完成前执行任何搜索时,系统会同时扫描该仅追加文件,以确保查询结果的准确性和一致性。

示例

以下是使用不同算法和参数创建索引的示例。有关所有可配置项的完整列表,请参阅索引选项参考指南。

-- HNSW 算法,默认配置。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops);

-- 使用 FP16 向量类型优化数据存储。

CREATE TABLE items (embedding vecf16(3));
CREATE INDEX ON items USING vectors (embedding vecf16_l2_ops)

--- 或者使用结合乘积量化(PQ)的暴力搜索(flat)。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.flat]
quantization.product.ratio = "x16"
$$);

-- 或者使用结合标量量化(SQ)的索引。
CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = "[indexing.hnsw.quantization.scalar]");

--- 或者使用 IVFPQ 算法。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
quantization.product.ratio = "x16"
$$);

-- 使用更多线程在后台构建索引。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
optimizing.optimizing_threads = 16
$$);

-- 倾向于使用更小的 HNSW 图分段。

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
segment.max_growing_segment_size = 200000
$$);

选择建议

选择合适的索引算法与参数对向量搜索性能至关重要。以下是针对不同使用场景的建议。

暴力搜索(flat)

如果您搜索的数据量较小(1,000 到 10,000 条之间)或需要完全精确的结果,暴力搜索(flat)算法是一个可行的选择。

该算法是衡量其他索引方法的基准。它以原始形式保留向量而不做压缩,避免了额外的索引开销。虽然简单直观、易于使用,但它不适合处理大规模数据集。

此外,您还可以使用量化来减少内存占用。有关所有选项的完整列表,请参阅量化选项文档。关于量化的详细介绍也可以在量化指南中找到。

倒排文件索引(IVF)

IVF 主要利用倒排索引的思想,将向量 (id, vector) 存储在各自的聚类中心下。查询向量时,它会先找到最近的若干个聚类中心,然后分别搜索这些中心下的向量。

在开始搜索之前,需要先通过 K-Means 聚类训练 nlist 个聚类中心,并可选择设置采样点数 nsample。较高的值能生成更精确的聚类结果,但需要更长的训练时间。有关更多设置,请参阅参考指南。

分层可导航小世界图(HNSW)

HNSW 将跳表(Skip List)与可导航小世界(NSW)图的思想相结合,采用多层图结构:在较高层使用较长的边以实现快速跳转搜索,在较低层使用较短的边以提高搜索精度。

可导航小世界(Navigable small world)

可导航小世界(NSW)基于这样一个前提:图中的任意节点都可以通过极少数的“跳数(hops)”从其他任何节点到达。选定入口点(无论是随机选择、启发式选择还是算法确定)后,算法会检查周围的邻居节点是否比当前节点更接近查询目标。搜索不断向最近的邻居推进,直到在当前节点的邻接列表中找不到更近的节点为止。

构建图时,参数 m 决定了新节点与其最近邻节点建立连接的数量。较高的 m 值会产生更稠密、互联程度更高的图,但也会增加内存占用并降低插入速度。在插入节点时,算法会找出最近的 m 个节点并与它们建立双向连接。

搜索过程从预定义的入口点开始遍历 NSW 图。算法以贪心方式移动到离查询向量更近的相邻顶点,一旦在当前顶点的邻接列表中找不到更近的邻居,搜索便停止。

NSW 搜索的一个局限在于,它总是选择通往最近节点的表面最短路径,而忽略了图的全局结构。这种“贪心搜索”方法容易陷入局部最优,这一问题通常被称为“早停(early stopping)”。

跳表(Skip list)

跳表使用概率方法构建:随着层级的升高,节点出现的概率逐渐降低,因此高层中的节点数量更少。搜索节点时,算法从顶层的“头节点”开始,向后移动到大于或等于目标的下一个节点;如果未找到目标节点,则下降到更低(更细粒度)的一层并重复此模式,直到定位到目标节点或其最近邻。

HNSW

构建这种多层图从包含整个数据集的基础层开始。沿着层级向上,每一层都提供其下一层的精简摘要,包含更少的节点,充当在图内进行大跨度跳跃的“快速通道”。

较小的 m 值更适合低维数据或对召回率要求不高的场景;较大的 m 值则适用于高维数据或对高召回率有严格要求的场景。

ef_construction 参数决定了添加新节点时的动态候选列表大小;增大该值可以提高召回率,但会延长索引构建时间。

HNSW 索引属于资源密集型索引,需要额外的内存,并且在处理大型数据集时需要调大 maintenance_work_mem 设置。如果您追求显著快于 IVF 的查询性能、高召回率以及随数据集规模良好扩展的能力,HNSW 是极佳的选择。

2 - 搜索

获取与目标向量最近的 5 个邻居

获取与目标向量最近的 5 个邻居:

SET vectors.hnsw_ef_search = 64;
SELECT * FROM items ORDER BY embedding <-> '[3,2,1]' LIMIT 5;

操作符

以下操作符用于表示不同的距离度量:

名称 说明
<-> 平方欧氏距离(Squared Euclidean distance)
<#> 负点积(Negative dot product)
<=> 余弦距离(Cosine distance)

有关它们的数学定义,请参阅概览。

过滤查询

在指定类别下,获取与目标向量最近的 10 个邻居:

SELECT 1 FROM items WHERE category_id = 1 ORDER BY embedding <#> '[0.5,0.5,0.5]' limit 10

查询选项

在当前会话中将 ivf 扫描列表数设置为 1:

SET vectors.ivf_nprobe=1;

在当前事务中将 hnsw 搜索范围设置为 40:

SET LOCAL vectors.hnsw_ef_search=40;

有关所有可配置项,请参阅搜索选项。

搜索模式

支持两种搜索模式:vbase 和 basic。

vbase

作为默认搜索模式,vbase 适用于绝大多数场景。

关于其工作原理,请参阅论文 VBASE: Unifying Online Vector Similarity Search and Relational Queries via Relaxed Monotonicity。

basic

basic 模式在行为上与 faiss 等向量算法库保持一致。如果您希望将结果与这些库对齐,该模式会很有帮助。

启用 basic 模式时,必须遵守以下限制:

  • 在执行更新操作后、进行搜索前,必须先执行 VACUUM。
  • 搜索时不能带有过滤条件(Filter)。

3 - 监控

我们提供了 pg_vector_index_stat 视图来监控索引构建进度。

我们提供了 pg_vector_index_stat 视图来监控索引的构建进度。

列名 类型 说明
tablerelid oid 表的 OID。
indexrelid oid 索引的 OID。
tablename name 表名。
indexname name 索引名。
idx_status text 取值为 NORMAL 或 UPGRADE,表示该索引处于正常状态还是需要升级。
idx_indexing bool 当 idx_status 为 NORMAL 时非空。表示后台线程是否正在构建索引。
idx_tuples int8 当 idx_status 为 NORMAL 时非空。索引中的元组总数。
idx_sealed int8[] 当 idx_status 为 NORMAL 时非空。每个已封存分段(sealed segment)中的元组数量。
idx_growing int8[] 当 idx_status 为 NORMAL 时非空。每个增长中分段(growing segment)中的元组数量。
idx_write int8 当 idx_status 为 NORMAL 时非空。写缓冲区(write buffer)中的元组数量。
idx_size int8 当 idx_status 为 NORMAL 时非空。所有分段占用的总字节大小。
idx_options text 当 idx_status 为 NORMAL 时非空。索引的配置信息。
idx_options 详情

idx_options 是一个包含向量(vector)、分段(segment)、优化(optimizing)和索引(indexing)选项的 JSON 字符串。示例如下:

{
    "vector": {
        "dimensions": 256,
        "distance": "L2",
        "kind": "F32"
    },
    "segment": {
        "max_growing_segment_size": 20000,
        "max_sealed_segment_size": 1000000
    },
    "optimizing": {
        "sealing_secs": 60,
        "sealing_size": 1,
        "delete_threshold": 0.2,
        "optimizing_threads": 2
    },
    "indexing": {
        "hnsw": {
            "m": 12,
            "ef_construction": 300,
            "quantization": {
                "trivial": {}
            }
        }
    }
}

如果不再有新的插入操作,并且您希望等待所有索引构建完成,可以每隔 60 秒检查一次 idx_indexing 字段,直到其变为 false(注:原文写作 true,实际 idx_indexing = false 表示后台索引构建已完成)。

4 - 量化

量化是一种在可接受的精度损失下降低内存消耗与计算复杂度的技术。在向量搜索场景中,主要有乘积量化和标量量化两种类型。

量化(Quantization)是一种在可接受的精度损失范围内,降低内存消耗和计算复杂度的技术。在向量搜索中,主要有两种量化方式:乘积量化(Product Quantization)和标量量化(Scalar Quantization)。

乘积量化(Product Quantization)

乘积量化通过在数据集上训练多个子空间聚类模型,将每个高维向量映射为一组聚类中心索引列表。它常用于解决高维向量数据内存占用过高的问题。

在 pgvecto.rs 中,您可以通过以下方式启用乘积量化:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = $$
[indexing.ivf]
quantization.product.ratio = "x16"
$$);

更多配置可在索引文档中找到。

标量量化(Scalar Quantization)

标量量化将每个标量值映射为低比特格式(例如从 32 位浮点数映射为 8 位整数)。通常情况下,这不会对精度造成明显影响,但能有效降低内存占用并提升查询性能。pgvecto.rs 支持通过以下方式启用 uint8 标量量化:

CREATE INDEX ON items USING vectors (embedding vector_l2_ops)
WITH (options = "[indexing.hnsw.quantization.scalar]");

选择建议

当需要使用量化时,建议始终优先尝试标量量化。它在精度与内存占用之间取得了良好的平衡。

如果内存占用仍然过高,可以尝试乘积量化。该方法能大幅降低内存占用,但可能会影响精度。由于乘积量化的效果与训练数据集高度相关,您需要尝试不同的 ratio 和 sample 参数值,以找到最适合您数据集的配置。

此外,您还可以使用主成分分析(PCA)等降维方法来降低向量维度。

5 - `pgvector` 兼容性

pgvecto.rs 对 pgvector 的原生兼容性与兼容模式配置。

pgvecto.rs 在以下方面原生兼容 pgvector:

  • CREATE TABLE 命令,例如 CREATE TABLE t (val vector(3))
  • INSERT INTO 命令,例如 INSERT INTO t (val) VALUES ('[0.6,0.6,0.6]')

pgvecto.rs 还可以通过配置兼容 pgvector 的以下语法:

  • 索引选项,允许通过 USING hnsw (val vector_ip_ops) 创建索引
  • 查询选项,支持通过 SET ivfflat.probes = 10 设置参数

该特性称为 pgvector 兼容模式,可通过 SET vectors.pgvector_compatibility = on 启用。

索引选项与查询选项

对于 ivfflat 和 hnsw 索引,仅支持以下选项:

  • 索引选项的默认值与 pgvecto.rs 原生默认值不同,而是与 pgvector 保持一致。
  • 查询选项的默认值则与 pgvecto.rs 保持一致。

ivfflat 的索引选项:

键名 类型 取值范围 默认值 说明
nlist integer [1, 1_000_000] 100 聚类单元(列表)的数量。

ivfflat 的查询选项:

选项 类型 取值范围 默认值 说明
ivfflat.probes integer [1, 1_000_000] 10 扫描的列表数量。
警告

ivfflat.probes 的默认值为 10,而非 pgvector 中的 1。

hnsw 的索引选项:

键名 类型 取值范围 默认值 说明
m integer [4, 128] 16 节点的最大出度。
ef_construction integer [10, 2000] 64 构建索引时的搜索范围。

hnsw 的查询选项:

选项 类型 取值范围 默认值 说明
hnsw.ef_search integer [1, 65535] 100 HNSW 的搜索范围。
警告

hnsw.ef_search 的默认值为 100,而非 pgvector 中的 40。

示例

启用 pgvector 兼容模式并开始向量查询非常简单:

DROP TABLE IF EXISTS t;
SET vectors.pgvector_compatibility=on;
SET hnsw.ef_search=40;
CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
CREATE INDEX hnsw_cos_index ON t USING hnsw (val vector_cosine_ops);
SELECT COUNT(1) FROM (SELECT 1 FROM t ORDER BY val <-> '[0.5,0.5,0.5]' limit 100) t2;
DROP INDEX hnsw_cos_index;

支持创建多种类型的索引:

SET vectors.pgvector_compatibility=on;
SET hnsw.ef_search=40;
-- 使用默认选项的 [hnsw + vector_l2_ops] 索引
CREATE INDEX hnsw_l2_index ON t USING hnsw (val vector_l2_ops);
-- 指定单个 ef_construction 选项的 [hnsw + vector_cosine_ops] 索引
CREATE INDEX hnsw_cosine_index ON t USING hnsw (val vector_cosine_ops) WITH (ef_construction = 80);
-- 指定全部选项的匿名 [hnsw + vector_ip_ops] 索引
CREATE INDEX ON t USING hnsw (val vector_ip_ops) WITH (ef_construction = 80, m = 12);
SET ivfflat.probes=1;
-- 使用默认选项的 [ivfflat + vector_l2_ops] 索引
CREATE INDEX ivfflat_l2_index ON t USING ivfflat (val vector_l2_ops);
-- 指定全部选项的 [ivfflat + vector_ip_ops] 索引
CREATE INDEX ivfflat_ip_index ON t USING ivfflat (val vector_cosine_ops) WITH (nlist = 80);
-- 指定全部选项的匿名 [ivf + vector_ip_ops] 索引
CREATE INDEX ON t USING ivfflat (val vector_ip_ops) WITH (lists = 80)