跳转到主要内容

索引构建

VectorChord 的 vchordrq 索引类型会将向量划分为多个列表,并仅搜索距离查询向量最近的一部分列表。它兼具极快的构建速度与低内存占用,同时性能显著优于 hnsw 和 ivfflat。

VectorChord 的索引类型 vchordrq 会将向量划分为多个列表(list),并仅搜索距离查询向量最近的一部分列表。它兼具极快的构建速度与低内存占用,同时在性能上显著优于 hnsw 和 ivfflat。

我们首先创建一个名为 items 的表,其中包含一个类型为 vector(n) 的 embedding 列,然后向其中填充示例数据。

CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
INSERT INTO items (embedding) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);

要创建 vchordrq 索引,可以使用以下 SQL 语句:

CREATE INDEX ON items USING vchordrq (embedding vector_l2_ops);

索引构建完成后,您就可以使用它执行向量搜索,检索距离目标向量最近的 1010 个近邻。

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

您还可以根据需要为向量搜索查询添加过滤条件。

SELECT * FROM items WHERE id % 7 <> 0 ORDER BY embedding <-> '[3,1,2]' LIMIT 10;

调优

当表中的行数少于 100,000100,000 时,通常不需要设置索引选项。

CREATE INDEX ON items USING vchordrq (embedding vector_l2_ops);

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

然而,随着行数的增长,对向量空间进行分区变得十分必要,这可以通过索引选项进行配置。options 使用 TOML(Tom’s Obvious Minimal Language) 格式的字符串指定。更多信息请参阅 #索引选项。

CREATE INDEX ON items USING vchordrq (embedding vector_l2_ops) WITH (options = $$
[build.internal]
lists = [1000]
$$);

SET vchordrq.probes TO '10';
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 10;

参数 lists 应根据表中的行数进行调优。下表提供了选择合适取值的指导建议。在执行查询时,请相应地选择 vchordrq.probes。

行数 NN 推荐分区数 LL lists 示例
N∈[0,105)N \in [0, 10^5) N/A []
N∈[105,2×106)N \in [10^5, 2 \times 10^6) L=N500L = \frac{N}{500} [2000]
N∈[2×106,5×107)N \in [2 \times 10^6, 5 \times 10^7) L∈[4N,8N]L \in [4 \sqrt{N}, 8 \sqrt{N}] [10000]
N∈[5×107,∞)N \in [5 \times 10^7, \infty) L∈[8N,16N]L \in [8 \sqrt{N}, 16\sqrt{N}] [80000]

构建索引的过程包含两个步骤:首先对向量空间进行分区,然后将行插入到索引中。第一步(划分向量空间)可以使用多线程进行加速。

CREATE INDEX ON items USING vchordrq (embedding vector_l2_ops) WITH (options = $$
[build.internal]
lists = [1000]
build_threads = 8
$$);

SET vchordrq.probes TO '10';
SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 10;

第二步(插入数据行)可以使用多进程并行执行。详情请参阅 PostgreSQL 调优。

对于大多数使用余弦相似度的数据集,启用 residual_quantization 和 build.internal.spherical_centroids 可以同时提升 QPS 和召回率(recall)。

CREATE INDEX ON items USING vchordrq (embedding vector_cosine_ops) WITH (options = $$
residual_quantization = true
[build.internal]
lists = [1000]
spherical_centroids = true
build_threads = 8
$$);

SET vchordrq.probes TO '10';
SELECT * FROM items ORDER BY embedding <=> '[3,1,2]' LIMIT 10;

为了提高构建速度,您可以选择将 build.pin 设置为 2,通过使用更多共享内存来加速构建过程。

CREATE INDEX ON items USING vchordrq (embedding vector_l2_ops) WITH (options = $$
residual_quantization = true
build.pin = 2
[build.internal]
lists = [1000]
spherical_centroids = true
build_threads = 8
$$);

对于大表而言,时间和内存开销主要由分区阶段主导。有关进一步的调优方法,请参阅分区调优。

您也可以参考外部构建,将分区阶段卸载到其他机器上执行。

参考

操作符类(vchordrq)

vchordrq

下表列出了 vchordrq 支持的所有可用操作符类(Operator Classes)。

操作符类 说明 操作符 1 操作符 2
vector_l2_ops 适用于 vector 类型与欧氏距离 <->(vector,vector) <<->>(vector,vector)
vector_ip_ops 适用于 vector 类型与负内积 <#>(vector,vector) <<#>>(vector,vector)
vector_cosine_ops 适用于 vector 类型与余弦距离 <=>(vector,vector) <<=>>(vector,vector)
halfvec_l2_ops 适用于 halfvec 类型与欧氏距离 <->(halfvec,halfvec) <<->>(halfvec,halfvec)
halfvec_ip_ops 适用于 halfvec 类型与负内积 <#>(halfvec,halfvec) <<#>>(halfvec,halfvec)
halfvec_cosine_ops 适用于 halfvec 类型与余弦距离 <=>(halfvec,halfvec) <<=>>(halfvec,halfvec)
rabitq8_l2_ops 适用于 rabitq8 类型与欧氏距离 <->(rabitq8,rabitq8) <<->>(rabitq8,rabitq8)
rabitq8_ip_ops 适用于 rabitq8 类型与负内积 <#>(rabitq8,rabitq8) <<#>>(rabitq8,rabitq8)
rabitq8_cosine_ops 适用于 rabitq8 类型与余弦距离 <=>(rabitq8,rabitq8) <<=>>(rabitq8,rabitq8)
rabitq4_l2_ops 适用于 rabitq4 类型与欧氏距离 <->(rabitq4,rabitq4) <<->>(rabitq4,rabitq4)
rabitq4_ip_ops 适用于 rabitq4 类型与负内积 <#>(rabitq4,rabitq4) <<#>>(rabitq4,rabitq4)
rabitq4_cosine_ops 适用于 rabitq4 类型与余弦距离 <=>(rabitq4,rabitq4) <<=>>(rabitq4,rabitq4)
vector_maxsim_ops 适用于 vector[] 类型与可扩展的多向量相似度 @#(vector[],vector[]) N/A
halfvec_maxsim_ops 适用于 halfvec[] 类型与可扩展的多向量相似度 @#(halfvec[],halfvec[]) N/A
rabitq8_maxsim_ops 适用于 rabitq8[] 类型与可扩展的多向量相似度 @#(rabitq8[],rabitq8[]) N/A
rabitq4_maxsim_ops 适用于 rabitq4[] 类型与可扩展的多向量相似度 @#(rabitq4[],rabitq4[]) N/A

<->、<#> 和 <=> 是由 pgvector 定义的操作符。

名称 说明
<-> 欧氏距离的平方
<#> 负点积
<=> 余弦距离

<<->>、<<#>>、<<=>> 是由 VectorChord 定义的操作符。有关 <<->>、<<#>>、<<=>> 的更多信息,请参阅相似度过滤。

@# 是由 VectorChord 定义的操作符。有关 @# 的更多信息,请参阅多向量检索。

rabitq8、rabitq4 是由 VectorChord 定义的数据类型。有关 rabitq8、rabitq4 的更多信息,请参阅量化类型。

MaxSim 相关的操作符类自 0.3.0 版本起可用。rabitq8 和 rabitq4 相关的操作符类自 1.1.0 版本起可用。

索引选项(vchordrq)

vchordrq

residual_quantization

  • 说明:该选项决定是否使用残差量化(Residual Quantization)。如果您不熟悉残差量化,可以阅读这篇博客了解更多信息。简而言之,残差量化是一种通过对向量的残差进行量化来提高向量搜索精度的技术。
  • 类型:布尔值(boolean)
  • 默认值:false
  • 示例:
    • residual_quantization = false 表示不使用残差量化。
    • residual_quantization = true 表示使用残差量化。
  • 注意:该选项不支持 rabitq8 和 rabitq4。

degree_of_parallelism

since v1.0.0
  • 说明:该选项是一个指定并行度的提示值。在大多数情况下,您无需更改它。如果您使用的 CPU 拥有超过 32 个线程,并且希望在 PostgreSQL 中利用更多线程,可以将其设置为线程数量以获得更好的性能。
  • 类型:整数(integer)
  • 默认值:32
  • 取值范围:[1, 256]
  • 示例:
    • degree_of_parallelism = 32 提示索引最多可能有 32 个进程并发访问该索引。
    • degree_of_parallelism = 64 提示索引最多可能有 64 个进程并发访问该索引。

build.pin

since v0.2.1
  • 说明:该选项决定构建索引时是否使用共享内存。对于大表,您可以选择启用此选项以加速构建过程。
  • 类型:整数与布尔值的联合类型(union of integer and boolean)
  • 默认值:
    • -1 since v1.0.0
    • false until v0.5.3
  • 取值范围:
    • {-1, 0, 1, 2, false, true} since v1.0.0
    • {false, true} until v0.5.3
  • 示例:
    • build.pin = 2 表示将索引的热数据部分全部缓存在内存中。
    • build.pin = 1 表示将索引热数据部分的一个子集缓存在内存中,消耗较少内存。
    • build.pin = 0 表示启用该功能但实际上不缓存任何内容。此选项仅用于调试目的。
    • build.pin = -1 表示禁用该功能。
    • build.pin = false 是 build.pin = -1 的旧版写法。
    • build.pin = true 是 build.pin = 1 的旧版写法。

默认构建选项

since v0.5.3

这是索引构建的默认设置。索引将不会进行分区。从语义上讲,build.default = {} 类似于 build.internal.lists = []。

内部构建选项(vchordrq)

vchordrq

build.internal.lists

  • 说明:该选项决定向量空间分区的层次结构。
  • 类型:整数列表(list of integers)
  • 默认值:
    • [] since v0.3.0
    • [1000] until v0.2.2: implicit behavior is not ideal
  • 示例:
    • build.internal.lists = [] 表示不对向量空间进行分区。
    • build.internal.lists = [4096] 表示将向量空间划分为 40964096 个单元(cell)。
    • build.internal.lists = [4096, 262144] 表示将向量空间划分为 40964096 个单元,并进一步将这些单元细分为 262144262144 个更小的单元。
  • 注意:索引会基于质心(centroid)将向量空间划分为多个 Voronoi 单元,通过迭代构建出一棵层次化的空间分区树。树中的每个叶子节点代表一个区域,并关联一个存储该区域内向量的列表。在插入时,向量会被放入其对应叶子节点的列表中。在查询时,索引通过排除叶子节点距离查询向量较远的列表来优化搜索,从而有效剪枝搜索空间。如果 lists 的长度为 11,则 lists 选项的值不应小于 4N4\sqrt{N},其中 NN 是表中的向量数量。

build.internal.spherical_centroids

  • 说明:该选项决定是否执行球面 K-means(spherical K-means)——即在每次迭代后对质心进行 L2 归一化,您可以参考此处的 spherical 选项。
  • 类型:布尔值(boolean)
  • 默认值:false
  • 示例:
    • build.internal.spherical_centroids = false 表示不执行球面 K-means。
    • build.internal.spherical_centroids = true 表示执行球面 K-means。
  • 注意:如果您的模型生成的嵌入向量使用余弦相似度作为度量标准,请将此项设置为 true。

build.internal.sampling_factor

since v0.2.0
  • 说明:该选项决定 K-means 算法为每个聚类采样的向量数量。该值越高,构建速度越慢,构建时的内存消耗越大,但搜索性能越好。
  • 类型:整数(integer)
  • 取值范围:[0, 1024]
  • 默认值:256
  • 示例:
    • build.internal.sampling_factor = 256 表示 K-means 算法采样 256C256 C 个向量,其中 CC 是 build.internal.lists 中的最大值。
    • build.internal.sampling_factor = 32 表示 K-means 算法采样 32C32 C 个向量,其中 CC 是 build.internal.lists 中的最大值。这会将 K-means 的时间和内存开销降低至默认值 256 时的约 18\frac{1}{8}。

build.internal.kmeans_iterations

since v0.2.2
  • 说明:该选项决定 K-means 算法的迭代次数。该值越高,构建速度越慢。
  • 类型:整数(integer)
  • 取值范围:[0, 1024]
  • 默认值:10
  • 示例:
    • build.internal.kmeans_iterations = 10 表示 K-means 算法执行 1010 次迭代。
    • build.internal.kmeans_iterations = 100 表示 K-means 算法执行 100100 次迭代。

build.internal.build_threads

  • 说明:该选项决定 K-means 算法使用的线程数。该值越高,构建速度越快,同时构建期间服务器的负载也越高。
  • 类型:整数(integer)
  • 取值范围:[1, 255]
  • 默认值:1
  • 示例:
    • build.internal.build_threads = 1 表示 K-means 算法使用 11 个线程。
    • build.internal.build_threads = 4 表示 K-means 算法使用 44 个线程。

build.internal.kmeans_algorithm

since v1.0.0
  • 说明:该选项决定所使用的 K-means 算法。
  • 类型:对象(object)
  • 示例:
    • build.internal.kmeans_algorithm.lloyd = {}:使用劳埃德算法(Lloyd’s algorithm)。这是默认值。
    • build.internal.kmeans_algorithm.hierarchical = {}:使用层次聚类(hierarchical clustering)。与劳埃德算法相比,这种方法速度要快得多,但可能会导致一定的精度损失。

build.internal.kmeans_dimension

since v1.0.0
  • 说明:该选项决定用于 K-means 输入和输出的维度。该特性通过重采样实现降维与升维,从而有效降低 K-means 的时间和内存消耗,但可能会导致一定的精度损失。
  • 类型:整数与空值的联合类型(union of integer and null)
  • 默认值:null
  • 示例:
    • 如果未设置此选项,则禁用该特性。
    • build.internal.kmeans_dimension = 100 表示 K-means 将处理 100100 维的向量。对于原始维度为 900900 的向量,这会将 K-means 的时间和内存开销降低至未启用该特性时的约 19\frac{1}{9}。

搜索参数(vchordrq)

vchordrq

vchordrq.enable_scan

since v0.5.0
  • 说明:启用或禁用查询规划器对 vchordrq 索引扫描的使用。该参数仅用于测试目的。
  • 类型:布尔值(boolean)
  • 默认值:on
  • 示例:
    • vchordrq.enable_scan = off 禁用查询规划器使用 vchordrq 索引扫描。
    • vchordrq.enable_scan = on 启用查询规划器使用 vchordrq 索引扫描。

vchordrq.probes

  • 说明:GUC 参数 vchordrq.probes 控制如何利用向量空间分区来辅助查询剪枝。探测数(probes)越多,搜索结果越精确,但速度也越慢。
  • 类型:整数列表(list of integers)
  • 默认值:
    • since v0.3.0
    • 10 until v0.2.2: the default value was 10 before `lists` defaulted to empty
    • 该参数支持回退参数(fallback)。
  • 示例:
    • SET vchordrq.probes = 1 表示仅使用 1 个探测分区。
    • SET vchordrq.probes = 10 表示使用 10 个探测分区。
  • 注意:默认值为空列表。该选项的长度必须与 lists 的长度相匹配。
    • 每项必须是 0..=4294967295 范围内的无符号十进制整数,空格会被忽略。空项(如 ,5、1,,2、10,)和溢出值会在规划或执行扫描时报错。索引的 probes 回退参数也遵循相同规则。
    • 如果 lists = [],则 probes 必须为 。
    • 如果 lists = [11, 22],则 probes 可以是 2,4 或 4,8。其形状不能不匹配,例如不能是 、3、7,8,9 或 5,5,5,5。

vchordrq.epsilon

  • 说明:即使经过剪枝,检索出的候选向量数量依然可观。索引采用 RaBitQ 算法将向量量化为位向量(bit vector),其内存占用仅为单精度浮点向量的 132\frac{1}{32}。由于大部分计算基于整数完成,处理速度得以大幅提升。与传统量化算法不同,RaBitQ 不仅估算距离,还会估算距离的下界(lower bound)。索引会计算每个向量的距离下界,并根据查询需求动态调整需要重新计算精确距离的向量数量,从而在性能与精度之间取得平衡。GUC 参数 vchordrq.epsilon 控制距离下界估算的保守程度。该值越高,精度越高,但性能越差。对于大多数应用场景而言,默认值倾向于提供高于实际所需的精度,因此您可以尝试调低此参数以获得更好的性能。
  • 类型:实数(real)
  • 默认值:
  • 取值范围:[0.0, 4.0]
  • 示例:
    • SET vchordrq.epsilon = 0.1 表示您正在使用非常乐观的下界估算。这样设置是因为在您所需的精度下,您的数据集对下界估算不敏感。
    • SET vchordrq.epsilon = 4.0 表示您正在使用非常悲观的下界估算。这样设置是因为在您所需的精度下,您的数据集对下界估算较为敏感。

vchordrq.prewarm_dim

removed in v0.4.0
  • 说明:GUC 参数 vchordrq.prewarm_dim 用于为指定维度预计算 RaBitQ 投影矩阵。这有助于降低 PostgreSQL 集群启动后首次查询的延迟。
  • 类型:整数列表(list of integers)
  • 默认值:64,128,256,384,512,768,1024,1536
  • 示例:
    • ALTER SYSTEM SET vchordrq.prewarm_dim = '64,128' 表示将为维度 64 和 128 预计算投影矩阵。
  • 注意:
    • 此设置需要重启数据库才能生效。
    • 自 v0.4.0 起,新算法已使该选项不再需要。

vchordrq.max_scan_tuples

since v0.2.0
  • 说明:GUC 参数 vchordrq.max_scan_tuples 控制在一次向量搜索中最多可以扫描的元组(tuple)数量。在大多数情况下,您不需要设置此参数,因为 LIMIT 子句起到了类似的作用。然而,当存在 WHERE 子句时,LIMIT 子句在过滤之后生效,而此 GUC 参数在过滤之前生效。该参数旨在防止过滤选择率极低时出现性能下降。
  • 类型:整数(integer)
  • 默认值:-1
  • 取值范围:[-1, 2147483647]
  • 示例:
    • SET vchordrq.max_scan_tuples = 999 表示索引扫描最多返回 999999 个元组。
    • SET vchordrq.max_scan_tuples = -1 表示对元组数量没有限制。
  • 注意:当设置为 -1 时,此参数不起作用。