这是本节的多页打印视图。 .
使用指南
- 1: 索引构建
- 2: 多向量检索
- 3: 图索引
- 4: 量化类型
- 5: 相似度过滤
- 6: PostgreSQL 调优
- 7: 监控
- 8: 回退参数
- 9: 评估召回率
- 10: 预热
- 11: 预过滤
- 12: 预取
- 13: 表内重排序
- 14: 分区调优
- 15: 外部构建
1 - 索引构建
VectorChord 的索引类型 vchordrq 会将向量划分为多个列表(list),并仅搜索距离查询向量最近的一部分列表。它兼具极快的构建速度与低内存占用,同时在性能上显著优于 hnsw 和 ivfflat。
我们首先创建一个名为 items 的表,其中包含一个类型为 vector(n) 的 embedding 列,然后向其中填充示例数据。
要创建 vchordrq 索引,可以使用以下 SQL 语句:
索引构建完成后,您就可以使用它执行向量搜索,检索距离目标向量最近的 个近邻。
您还可以根据需要为向量搜索查询添加过滤条件。
调优
当表中的行数少于 时,通常不需要设置索引选项。
然而,随着行数的增长,对向量空间进行分区变得十分必要,这可以通过索引选项进行配置。options 使用 TOML(Tom’s Obvious Minimal Language) 格式的字符串指定。更多信息请参阅 #索引选项。
参数 lists 应根据表中的行数进行调优。下表提供了选择合适取值的指导建议。在执行查询时,请相应地选择 vchordrq.probes。
| 行数 | 推荐分区数 | lists 示例 |
|---|---|---|
| N/A | [] |
|
[2000] |
||
[10000] |
||
[80000] |
构建索引的过程包含两个步骤:首先对向量空间进行分区,然后将行插入到索引中。第一步(划分向量空间)可以使用多线程进行加速。
第二步(插入数据行)可以使用多进程并行执行。详情请参阅 PostgreSQL 调优。
对于大多数使用余弦相似度的数据集,启用 residual_quantization 和 build.internal.spherical_centroids 可以同时提升 QPS 和召回率(recall)。
为了提高构建速度,您可以选择将 build.pin 设置为 2,通过使用更多共享内存来加速构建过程。
对于大表而言,时间和内存开销主要由分区阶段主导。有关进一步的调优方法,请参阅分区调优。
您也可以参考外部构建,将分区阶段卸载到其他机器上执行。
参考
操作符类(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)
- 默认值:
-1since v1.0.0falseuntil 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]表示将向量空间划分为 个单元(cell)。build.internal.lists = [4096, 262144]表示将向量空间划分为 个单元,并进一步将这些单元细分为 个更小的单元。
- 注意:索引会基于质心(centroid)将向量空间划分为多个 Voronoi 单元,通过迭代构建出一棵层次化的空间分区树。树中的每个叶子节点代表一个区域,并关联一个存储该区域内向量的列表。在插入时,向量会被放入其对应叶子节点的列表中。在查询时,索引通过排除叶子节点距离查询向量较远的列表来优化搜索,从而有效剪枝搜索空间。如果
lists的长度为 ,则lists选项的值不应小于 ,其中 是表中的向量数量。
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 算法采样 个向量,其中 是build.internal.lists中的最大值。build.internal.sampling_factor = 32表示 K-means 算法采样 个向量,其中 是build.internal.lists中的最大值。这会将 K-means 的时间和内存开销降低至默认值256时的约 。
build.internal.kmeans_iterations
since v0.2.2
- 说明:该选项决定 K-means 算法的迭代次数。该值越高,构建速度越慢。
- 类型:整数(integer)
- 取值范围:
[0, 1024] - 默认值:
10 - 示例:
build.internal.kmeans_iterations = 10表示 K-means 算法执行 次迭代。build.internal.kmeans_iterations = 100表示 K-means 算法执行 次迭代。
build.internal.build_threads
- 说明:该选项决定 K-means 算法使用的线程数。该值越高,构建速度越快,同时构建期间服务器的负载也越高。
- 类型:整数(integer)
- 取值范围:
[1, 255] - 默认值:
1 - 示例:
build.internal.build_threads = 1表示 K-means 算法使用 个线程。build.internal.build_threads = 4表示 K-means 算法使用 个线程。
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 将处理 维的向量。对于原始维度为 的向量,这会将 K-means 的时间和内存开销降低至未启用该特性时的约 。
搜索参数(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.010until 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),其内存占用仅为单精度浮点向量的 。由于大部分计算基于整数完成,处理速度得以大幅提升。与传统量化算法不同,RaBitQ 不仅估算距离,还会估算距离的下界(lower bound)。索引会计算每个向量的距离下界,并根据查询需求动态调整需要重新计算精确距离的向量数量,从而在性能与精度之间取得平衡。GUC 参数
vchordrq.epsilon控制距离下界估算的保守程度。该值越高,精度越高,但性能越差。对于大多数应用场景而言,默认值倾向于提供高于实际所需的精度,因此您可以尝试调低此参数以获得更好的性能。 - 类型:实数(real)
- 默认值:
1.9- 该参数支持回退参数(fallback)。
- 取值范围:
[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表示索引扫描最多返回 个元组。SET vchordrq.max_scan_tuples = -1表示对元组数量没有限制。
- 注意:当设置为
-1时,此参数不起作用。
2 - 多向量检索
多向量检索(Multi-Vector Retrieval)是检索增强生成(RAG)系统中使用的一项高级技术,通过为每个文档关联多个向量来增强文档检索效果。
与单向量检索不同,使用 MaxSim 操作符的多向量检索能够捕捉细粒度的语义关系。
关于如何构建端到端的多向量检索应用,请参阅我们的博客了解更多细节。
快速上手
晚期交互(Late interaction)模型会将文档和查询分别嵌入为向量数组,并通过名为 MaxSim 的操作符计算相似度。MaxSim 操作符的定义为 ,即对于查询向量数组中的每个向量,在文档向量数组中找到最接近的向量,计算它们的点积,最后将所有结果求和。
在 VectorChord 中,该操作符的符号为 @#。左操作数是文档向量数组,右操作数是查询向量数组。原始的 MaxSim 操作符基于相似度,而 VectorChord 中的操作符则基于距离:VectorChord 的实现通过对相似度得分取反,将其解释为一种距离度量。
要为向量数组构建索引,首先创建一个名为 items 的表,其中包含一个类型为 vector(n)[] 的 embeddings 列,然后向表中填充生成的数据。
与其他向量操作符一样,使用 MaxSim 操作符的查询也可以通过索引进行加速。
随后,您便可以使用该索引执行向量搜索。
参考
vchordg 不支持此功能。
操作符类
请参阅:
搜索参数(vchordrq)
vchordrq
MaxSim 操作符的索引机制与其他向量操作符类似。当在向量数组列上构建索引时,数组中的每个向量都会被单独插入到索引数据结构中。在查询期间,索引会对查询数组中的每个向量分别执行一次独立的向量搜索。默认情况下,索引不会对结果进行重排序(rerank),而是使用 RaBitQ 的估算距离代替实际距离。随后,索引会将所有独立向量搜索的结果进行合并,以生成最终输出。
MaxSim 索引还使用了以下几个额外参数:
vchordrq.maxsim_refine
since v0.3.0
- 说明:GUC 参数
vchordrq.maxsim_refine会让索引对结果进行重排序,用实际距离替换 RaBitQ 的估算距离,直到前 k 个(top-k)最近向量的距离全部重新计算完毕。 - 类型:整数(integer)
- 取值范围:
[0, 2147483647] - 默认值:
0- 该参数支持回退参数(fallback)。
- 示例:
SET vchordrq.maxsim_refine = 0表示索引始终使用 RaBitQ 的估算距离。SET vchordrq.maxsim_refine = 1024表示索引会对结果进行重排序,用实际距离替换 RaBitQ 的估算距离,直到前 1024 个最近向量的距离全部重新计算完毕。
vchordrq.maxsim_threshold
since v0.3.0
- 说明:GUC 参数
vchordrq.maxsim_threshold启用对缺失值更激进的估算策略。启用该设置后,索引会在最底层列表中找到累积大小达到或超过vchordrq.maxsim_threshold的第一个聚类(cluster),并使用其与查询向量的距离来估算缺失值。 - 类型:整数(integer)
- 取值范围:
[0, 2147483647] - 默认值:
0- 该参数支持回退参数(fallback)。
- 示例:
SET vchordrq.maxsim_threshold = 0表示不启用更激进的估算策略。SET vchordrq.maxsim_threshold = 1024表示索引会在最底层列表中找到累积大小达到或超过1024的第一个聚类,并使用其与查询向量的距离来估算缺失值。
3 - 图索引
VectorChord 的索引类型 vchordg 是一种基于磁盘的图索引,具有低内存占用的特点。
我们首先创建一个名为 items 的表,其中包含一个类型为 vector(n) 的 embedding 列,然后向其中填充示例数据。
要创建 vchordg 索引,可以使用以下 SQL 语句:
该功能目前处于预览阶段。
调优
在构建索引时,通常需要对两个选项进行调优:m 和 ef_construction。m 是每个顶点的最大邻居数,ef_construction 是插入期间包含最近邻的动态列表大小。
在搜索时,需要对 ef_search 进行调优。ef_search 是搜索期间包含最近邻的动态列表大小。
作为一种基于磁盘的索引,vchordg 通常只需要将量化后的向量保存在缓冲池(buffer pool)中即可维持良好性能。默认情况下,vchordg 会将一个 维向量量化为 比特。设行数为 ,则该索引所需的总内存为 比特。如果您的内存非常有限且正在使用超高维向量,可以考虑将向量量化为 比特,此时索引所需的总内存为 比特。
索引构建可以通过使用多进程来加速。详情请参阅 PostgreSQL 调优。
参考
操作符类(vchordg)
vchordg
下表列出了 vchordg 支持的所有可用操作符类。
| 操作符类 | 说明 | 操作符 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) |
<<->>、<<#>>、<<=>> 是由 VectorChord 定义的操作符。有关 <<->>、<<#>>、<<=>> 的更多信息,请参阅相似度过滤。
rabitq8、rabitq4 是由 VectorChord 定义的数据类型。有关 rabitq8、rabitq4 的更多信息,请参阅量化类型。
前 6 个操作符类自 0.5.0 版本起可用,后 6 个操作符类自 1.1.0 版本起可用。
索引选项(vchordg)
vchordg
bits
since v0.5.0
- 说明:RaBitQ 量化后比特数与维度的比值。
bits = 2可提供更好的召回率和 QPS,而bits = 1则消耗更少的内存。 - 类型:整数(integer)
- 默认值:
2 - 示例:
bits = 2表示将 维向量量化为 比特。bits = 1表示将 维向量量化为 比特。
m
since v0.5.0
- 说明:每个顶点的最大邻居数。
m越大,性能越好,但存储需求也越高。m对应于 HNSW 中的 和 DiskANN 中的 。 - 类型:整数(integer)
- 默认值:
32 - 示例:
m = 32表示每个顶点最多有 个邻居。m = 64表示每个顶点最多有 个邻居。
ef_construction
since v0.5.0
- 说明:插入期间包含最近邻的动态列表大小。
ef_construction越大,搜索性能越好,但插入速度越慢。ef_construction对应于 HNSW 中的 和 DiskANN 中的 。 - 类型:整数(integer)
- 默认值:
64 - 示例:
ef_construction = 64表示在插入期间包含最近邻的动态列表大小为 。ef_construction = 128表示在插入期间包含最近邻的动态列表大小为 。
alpha
since v0.5.0
- 说明:剪枝期间所选用的
alpha值。alpha对应于 DiskANN 中的 。该选项必须包含 1–8 个升序排列的值,其中第一个元素为1.0,最后一个元素小于2.0,不允许空列表。 - 类型:浮点数列表(list of floats)
- 默认值:
[1.0, 1.2] - 示例:
alpha = [1.0, 1.2]等价于在 DiskANN 中设置alpha = 1.2。alpha = [1.0]等价于 HNSW 中的默认剪枝策略。
- 注意:当距离度量为负内积时,此选项无效。
beam_construction
since v0.5.0
- 说明:插入期间使用的束宽(beam width)。束宽是指一次同时访问的顶点数量。由于从 SSD 随机读取少量扇区的时间与读取单个扇区几乎相同,较大的束宽可以有效减少对 SSD 的往返访问次数,从而获得更好的性能。但由于它会增加计算量,因此对于完全放入内存的索引而言反而不利。
- 类型:整数(integer)
- 默认值:
1 - 示例:
beam_construction = 8表示索引在插入期间一次同时访问 8 个顶点。beam_construction = 1表示索引在插入期间一次访问 1 个顶点。
搜索参数(vchordg)
vchordg
vchordg.enable_scan
since v0.5.0
- 说明:启用或禁用查询规划器对
vchordg索引扫描的使用。该参数仅用于测试目的。 - 类型:布尔值(boolean)
- 默认值:
on - 示例:
vchordg.enable_scan = off禁用查询规划器使用vchordg索引扫描。vchordg.enable_scan = on启用查询规划器使用vchordg索引扫描。
vchordg.ef_search
since v0.5.0
- 说明:搜索期间包含最近邻的动态列表大小。
vchordg.ef_search越大,召回率越好,但 QPS 越低。vchordg.ef_search对应于 HNSW 和 DiskANN 中的 。 - 类型:整数(integer)
- 默认值:
64- 该参数支持回退参数(fallback)。
- 取值范围:
[1, 65535] - 示例:
SET vchordg.ef_search = 64表示在搜索期间包含最近邻的动态列表大小为 。SET vchordg.ef_search = 128表示在搜索期间包含最近邻的动态列表大小为 。
vchordg.beam_search
since v0.5.0
- 说明:搜索期间使用的束宽(beam width)。束宽是指一次同时访问的顶点数量。由于从 SSD 随机读取少量扇区的时间与读取单个扇区几乎相同,较大的束宽可以有效减少对 SSD 的往返访问次数,从而获得更好的性能。但由于它会增加计算量,因此对于完全放入内存的索引而言反而不利。
- 类型:整数(integer)
- 默认值:
1 - 取值范围:
[1, 65535] - 示例:
SET vchordg.beam_search = 8表示索引在搜索期间一次同时访问 8 个顶点。SET vchordg.beam_search = 1表示索引在搜索期间一次访问 1 个顶点。
vchordg.max_scan_tuples
since v0.5.0
- 说明:GUC 参数
vchordg.max_scan_tuples控制在一次向量搜索中最多可以扫描的元组数量。在大多数情况下,您不需要设置此参数,因为LIMIT子句起到了类似的作用。然而,当存在WHERE子句时,LIMIT子句在过滤之后生效,而此 GUC 参数在过滤之前生效。该参数旨在防止过滤选择率极低时出现性能下降。 - 类型:整数(integer)
- 默认值:
-1 - 取值范围:
[-1, 2147483647] - 示例:
SET vchordg.max_scan_tuples = 999表示索引扫描最多返回 个元组。SET vchordg.max_scan_tuples = -1表示对元组数量没有限制。
- 注意:当设置为
-1时,此参数不起作用。
4 - 量化类型
量化不仅可以应用于索引,还可以直接应用于向量本身。为此,VectorChord 提供了专门用于量化向量的数据类型。
rabitq8 是量化向量的数据类型。在相同维度下,它的存储开销约为全精度向量(vector)的 ,半精度向量(halfvec)的 。该类型以其所使用的量化算法命名。1
与普通向量类型一样,也可以在 rabitq8 类型的列上创建索引。
通过使用 rabitq8,表大小和索引大小都将缩小为使用 vector 类型时的 。其代价是量化会明显折损性能与精度。因此,它适用于检索大量候选结果的场景。
此外,VectorChord 还提供了 rabitq4 类型。rabitq8 量化向量在每个维度上使用 比特,而 rabitq4 量化向量在每个维度上仅使用 比特。它专为在受限环境下优先追求低存储开销而非高精度的场景而设计。
参考
操作符类
请参阅:
数据类型
rabitq8
since v1.1.0
- 说明:使用
uint8进行内部存储的量化向量数据类型。基于扩展版 RaBitQ 算法。1 - 文本表示:与
vector/halfvec不同,rabitq8的文本表示更为复杂。假设维度为 ,它由括在圆括号中的 个浮点数数组与括在方括号中的 个uint8整数数组拼接而成。例如(2.999999, 160.63234, 647, 2.8498733)[255, 205, 187]。其内部解释无需手动处理,但您可以通过dequantize_to_vector(rabitq8)/dequantize_to_halfvec(rabitq8)获取它所代表的近似向量。
rabitq4
since v1.1.0
- 说明:使用
uint4进行内部存储的量化向量数据类型。基于扩展版 RaBitQ 算法。1 - 文本表示:与
vector/halfvec不同,rabitq4的文本表示更为复杂。假设维度为 ,它由括在圆括号中的 个浮点数数组与括在方括号中的 个uint4整数数组拼接而成。例如(2.999999, 9.420722, 38, 2.8498733)[15, 12, 11]。其内部解释无需手动处理,但您可以通过dequantize_to_vector(rabitq4)/dequantize_to_halfvec(rabitq4)获取它所代表的近似向量。
函数
quantize_to_rabitq8(vector)
since v1.1.0
- 说明:将向量量化为
rabitq8。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。 - 返回值:
rabitq8,量化后的向量 - 参数:
vector,待量化的向量
- 示例:
SELECT quantize_to_rabitq8('[1,1,1]'::vector)
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (vector AS rabitq8) WITH FUNCTION quantize_to_rabitq8(vector) AS ASSIGNMENT手动创建。
quantize_to_rabitq8(halfvec)
since v1.1.0
- 说明:将半精度向量量化为
rabitq8。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。 - 返回值:
rabitq8,量化后的向量 - 参数:
halfvec,待量化的向量
- 示例:
SELECT quantize_to_rabitq8('[1,1,1]'::halfvec)
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (halfvec AS rabitq8) WITH FUNCTION quantize_to_rabitq8(halfvec) AS ASSIGNMENT手动创建。
dequantize_to_vector(rabitq8)
since v1.1.1
- 说明:将量化向量反量化为
vector。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。使用此函数无法精确重建原始向量。 - 返回值:
vector,反量化后的向量 - 参数:
rabitq8,待反量化的量化向量
- 示例:
SELECT dequantize_to_vector(quantize_to_rabitq8('[1,1,1]'::vector))
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (rabitq8 AS vector) WITH FUNCTION dequantize_to_vector(rabitq8) AS ASSIGNMENT手动创建。
dequantize_to_halfvec(rabitq8)
since v1.1.1
- 说明:将量化向量反量化为
halfvec。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。使用此函数无法精确重建原始向量。 - 返回值:
halfvec,反量化后的向量 - 参数:
rabitq8,待反量化的量化向量
- 示例:
SELECT dequantize_to_halfvec(quantize_to_rabitq8('[1,1,1]'::halfvec))
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (rabitq8 AS halfvec) WITH FUNCTION dequantize_to_halfvec(rabitq8) AS ASSIGNMENT手动创建。
quantize_to_rabitq4(vector)
since v1.1.0
- 说明:将向量量化为
rabitq4。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。 - 返回值:
rabitq4,量化后的向量 - 参数:
vector,待量化的向量
- 示例:
SELECT quantize_to_rabitq4('[1,1,1]'::vector)
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (vector AS rabitq4) WITH FUNCTION quantize_to_rabitq4(vector) AS ASSIGNMENT手动创建。
quantize_to_rabitq4(halfvec)
since v1.1.0
- 说明:将半精度向量量化为
rabitq4。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。 - 返回值:
rabitq4,量化后的向量 - 参数:
halfvec,待量化的向量
- 示例:
SELECT quantize_to_rabitq4('[1,1,1]'::halfvec)
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (halfvec AS rabitq4) WITH FUNCTION quantize_to_rabitq4(halfvec) AS ASSIGNMENT手动创建。
dequantize_to_vector(rabitq4)
since v1.1.1
- 说明:将量化向量反量化为
vector。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。使用此函数无法精确重建原始向量。 - 返回值:
vector,反量化后的向量 - 参数:
rabitq4,待反量化的量化向量
- 示例:
SELECT dequantize_to_vector(quantize_to_rabitq4('[1,1,1]'::vector))
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (rabitq4 AS vector) WITH FUNCTION dequantize_to_vector(rabitq4) AS ASSIGNMENT手动创建。
dequantize_to_halfvec(rabitq4)
since v1.1.1
- 说明:将量化向量反量化为
halfvec。请注意,在不同的 VectorChord 版本或不同的机器上,该函数对相同输入可能会产生略有差异的结果。使用此函数无法精确重建原始向量。 - 返回值:
halfvec,反量化后的向量 - 参数:
rabitq4,待反量化的量化向量
- 示例:
SELECT dequantize_to_halfvec(quantize_to_rabitq4('[1,1,1]'::halfvec))
- 注意:该函数没有对应的默认类型转换(
CAST)。如有需要,您可以使用CREATE CAST (rabitq4 AS halfvec) WITH FUNCTION dequantize_to_halfvec(rabitq4) AS ASSIGNMENT手动创建。
操作符
<->(rabitq8, rabitq8)
since v1.1.0
- 说明:计算两个量化向量之间的 L2 距离。
- 返回值:
real,两个量化向量的 L2 距离 - 参数:
rabitq8rabitq8
<#>(rabitq8, rabitq8)
since v1.1.0
- 说明:计算两个量化向量之间的负点积距离。
- 返回值:
real,两个量化向量的负点积距离 - 参数:
rabitq8rabitq8
<=>(rabitq8, rabitq8)
since v1.1.0
- 说明:计算两个量化向量之间的余弦距离。
- 返回值:
real,两个量化向量的余弦距离 - 参数:
rabitq8rabitq8
<->(rabitq4, rabitq4)
since v1.1.0
- 说明:计算两个量化向量之间的 L2 距离。
- 返回值:
real,两个量化向量的 L2 距离 - 参数:
rabitq4rabitq4
<#>(rabitq4, rabitq4)
since v1.1.0
- 说明:计算两个量化向量之间的负点积距离。
- 返回值:
real,两个量化向量的负点积距离 - 参数:
rabitq4rabitq4
<=>(rabitq4, rabitq4)
since v1.1.0
- 说明:计算两个量化向量之间的余弦距离。
- 返回值:
real,两个量化向量的余弦距离 - 参数:
rabitq4rabitq4
-
Jianyang Gao, Yutong Gou, Yuexuan Xu, Yongyi Yang, Cheng Long, Raymond Chi-Wing Wong, “Practical and Asymptotically Optimal Quantization of High-Dimensional Vectors in Euclidean Space for Approximate Nearest Neighbor Search”, SIGMOD 2025, available at https://arxiv.org/abs/2409.09913. ↩︎ ↩︎ ↩︎
5 - 相似度过滤
通常,我们会使用 ORDER BY 和 LIMIT 子句来执行向量搜索。但在某些情况下,您可能希望使用 WHERE 子句过滤掉距离查询向量太远的向量。很自然地,您可能会写出如下 SQL:
返回的结果虽然符合预期,但这种写法的性能却很差。这是因为如果落在距离阈值内的向量少于 个,PostgreSQL 会强制索引继续搜索以凑够 条结果,从而遍历耗尽整个搜索空间。
为了避免这种情况,您可以使用特定语法将过滤条件直接下推(push down)到向量索引中,使索引一旦发现搜索区域超出指定距离便立即停止搜索。我们将这种可以下推到索引中的基于距离的过滤条件称为相似度过滤(similarity filter)。
当且仅当两个向量之间的 L2 距离小于 0.1 时,embedding <<->> sphere('[0, 0, 0]'::vector, 0.1) 的求值结果为 true。对于负内积请使用 <<#>> 操作符,对于余弦距离请使用 <<=>> 操作符。
此外,如果您只指定 WHERE 子句而不使用 ORDER BY,索引依然能够被有效利用。
参考
操作符类
请参阅:
数据类型
sphere_vector
- 说明:球体(Sphere)。
- 定义:
(center vector, radius REAL)。
sphere_halfvec
- 说明:球体(Sphere)。
- 定义:
(center halfvec, radius REAL)。
sphere_rabitq8
since v1.1.0
- 说明:球体(Sphere)。
- 定义:
(center rabitq8, radius REAL)。
sphere_rabitq4
since v1.1.0
- 说明:球体(Sphere)。
- 定义:
(center rabitq4, radius REAL)。
函数
sphere(vector, real)
- 说明:通过球心和半径创建一个球体。
- 定义:
SELECT ROW($1, $2)。 - 返回值:
sphere_vector - 参数:
vector,球体的球心real,球体的半径
- 示例:
SELECT sphere('[0,0,0]'::vector, 1.0)
sphere(halfvec, real)
- 说明:通过球心和半径创建一个球体。
- 定义:
SELECT ROW($1, $2)。 - 返回值:
sphere_halfvec - 参数:
halfvec,球体的球心real,球体的半径
- 示例:
SELECT sphere('[0,0,0]'::halfvec, 1.0)
sphere(rabitq8, real)
since v1.1.0
- 说明:通过球心和半径创建一个球体。
- 定义:
SELECT ROW($1, $2)。 - 返回值:
sphere_rabitq8 - 参数:
rabitq8,球体的球心real,球体的半径
- 示例:
SELECT sphere(quantize_to_rabitq8('[0,0,0]'::vector), 1.0)
sphere(rabitq4, real)
since v1.1.0
- 说明:通过球心和半径创建一个球体。
- 定义:
SELECT ROW($1, $2)。 - 返回值:
sphere_rabitq4 - 参数:
rabitq4,球体的球心real,球体的半径
- 示例:
SELECT sphere(quantize_to_rabitq4('[0,0,0]'::vector), 1.0)
操作符
<<->>(vector, sphere_vector)
- 说明:使用 L2 度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
vector,待检测的向量sphere_vector,球体
<<#>>(vector, sphere_vector)
- 说明:使用负点积度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
vector,待检测的向量sphere_vector,球体
<<=>>(vector, sphere_vector)
- 说明:使用余弦度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
vector,待检测的向量sphere_vector,球体
<<->>(halfvec, sphere_halfvec)
- 说明:使用 L2 度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
halfvec,待检测的向量sphere_halfvec,球体
<<#>>(halfvec, sphere_halfvec)
- 说明:使用负点积度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
halfvec,待检测的向量sphere_halfvec,球体
<<=>>(halfvec, sphere_halfvec)
- 说明:使用余弦度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
halfvec,待检测的向量sphere_halfvec,球体
<<->>(rabitq8, sphere_rabitq8)
since v1.1.0
- 说明:使用 L2 度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq8,待检测的向量sphere_rabitq8,球体
<<#>>(rabitq8, sphere_rabitq8)
since v1.1.0
- 说明:使用负点积度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq8,待检测的向量sphere_rabitq8,球体
<<=>>(rabitq8, sphere_rabitq8)
since v1.1.0
- 说明:使用余弦度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq8,待检测的向量sphere_rabitq8,球体
<<->>(rabitq4, sphere_rabitq4)
since v1.1.0
- 说明:使用 L2 度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq4,待检测的向量sphere_rabitq4,球体
<<#>>(rabitq4, sphere_rabitq4)
since v1.1.0
- 说明:使用负点积度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq4,待检测的向量sphere_rabitq4,球体
<<=>>(rabitq4, sphere_rabitq4)
since v1.1.0
- 说明:使用余弦度量检测向量是否位于球体内部。
- 返回值:布尔值(boolean)
- 参数:
rabitq4,待检测的向量sphere_rabitq4,球体
6 - PostgreSQL 调优
索引构建可以并行化执行。由于并行化会增加服务器负载,PostgreSQL 默认并未启用。如需启用并行构建,请参考以下 SQL 配置:
并行工作进程的数量还取决于表本身的配置。默认情况下,该值由 PostgreSQL 自动计算决定。如果 PostgreSQL 判定不启用并行,那么并行索引构建就不会生效。您可以通过调整表的 parallel_workers 存储参数来覆盖 PostgreSQL 的默认行为,强制启用并行索引构建。
您还需要调整以下几个设置以提升 I/O 性能:
我们观察到在某些情况下,工作异常的 JIT(即时编译)可能会对性能产生负面影响。如果您并不依赖它,可以选择将其禁用。
最后,您需要设置 PostgreSQL 共享缓冲区(shared buffers)的大小。
异步 I/O
PostgreSQL 17 首次引入了流式 I/O(streaming I/O)API。在此基础上,PostgreSQL 18 引入了异步 I/O(Asynchronous I/O)。借助异步 I/O,PostgreSQL 可以同时发起多个读取操作,并在等待结果返回的同时继续处理其他任务。这能够显著提升需要从磁盘读取大量数据的查询性能。PostgreSQL 提供了多种异步 I/O 方式,您可以根据需要进行调整。
本节内容仅适用于 PostgreSQL 18。
io_uring
io_uring 方式使用 Linux 内核的 io_uring 接口,能够减少上下文切换的开销。该接口在 Linux 内核 5.1 及更高版本中可用。您可以通过 GUC 参数 io_method 启用此方式。
由于 io_uring 会使用额外的文件描述符和锁定内存(locked memory),PostgreSQL 可能会触及系统的 RLIMIT_NOFILE 和 RLIMIT_MEMLOCK 限制。您可能需要在操作系统层面调高这些限制。另请参阅:
出于安全考虑,容器运行时、云服务商或系统管理员可能会禁用 io_uring。如果遇到此类情况,请向您的服务提供商咨询详情。另请参阅:
通常推荐使用 io_uring,但实际性能表现仍取决于您的具体工作负载。另请参阅:
本小节仅适用于 Linux 系统。
worker
这是 PostgreSQL 18 中的默认设置。PostgreSQL 通过将 I/O 请求发送到专门的进程池来避免阻塞后端进程。您可以通过 GUC 参数 io_method 显式启用此方式。
sync
sync 方式的工作机制与 PostgreSQL 17 相同:读取操作是同步且阻塞的,并使用 posix_fadvise 向操作系统提供即将发生的读取提示。通常只有在调试时才会使用此方式。您可以通过 GUC 参数 io_method 启用此方式。
7 - 监控
您可以通过查询 pg_stat_progress_create_index 视图来监控 PostgreSQL 索引的构建进度。
参考
构建阶段(vchordrq)
vchordrq
vchordrq 的索引构建过程包含 5 个步骤,每个步骤的阶段名称(phase)如下:
| 步骤 | 阶段消息 | 说明 | 耗时 |
|---|---|---|---|
| 1 | initializing |
开始构建索引 | 短 |
| 2 | initializing index, by default build |
该步骤仅用于默认构建(default build) | 短 |
| 2 | initializing index, by internal build |
该步骤仅用于内部构建:对表进行采样并执行分区以获取质心 | 长 |
| 2 | initializing index, by external build |
该步骤仅用于外部构建:从指定表中加载质心 | 短 |
| 3 | initializing index |
初始化索引的数据结构与存储空间 | 短 |
| 4 | inserting tuples from table to index |
将所有向量分配并插入到各自对应的列表中 | 长 |
| 5 | compacting tuples in index |
紧缩优化向量索引结构以提升查询性能 | 中等 |
第 4 步 inserting tuples from table to index 占据了索引构建过程中的大部分时间。tuples_done、blocks_done 和 blocks_total 列可用于指示该步骤的当前进度。
构建阶段(vchordg)
vchordg
vchordg 的索引构建过程包含 2 个步骤,每个步骤的阶段名称(phase)如下:
| 步骤 | 阶段消息 | 说明 | 耗时 |
|---|---|---|---|
| 1 | initializing |
开始构建索引 | 短 |
| 2 | inserting tuples from table to index |
将所有向量插入到图结构中 | 长 |
第 2 步 inserting tuples from table to index 占据了索引构建过程中的大部分时间。tuples_done、blocks_done 和 blocks_total 列可用于指示该步骤的当前进度。
8 - 回退参数
搜索参数通常通过 GUC 进行配置。VectorChord 还提供了另一种方式,允许将这些参数直接绑定到索引上,而不是绑定到会话(session)或事务(transaction)上。换句话说,您可以将它们作为索引存储参数(storage parameters)的一部分保存下来。
在构建索引时,您可以使用以下语法设置 probes:
现在,您无需设置 GUC 即可直接执行搜索:
如果索引已经构建完成,您可以像这样修改 probes:
您仍然可以通过 GUC 设置搜索参数。当设置了 GUC 时,GUC 的优先级高于索引存储参数。这使您能够在不锁定索引的情况下,在当前会话或事务中临时覆盖这些设置。
这种语法在以下场景中非常有用:
- 当存在多个索引且需要不同参数时。
- 当您不希望设置 GUC 时。
- 当您希望离线配置搜索参数时。
只要设置了 GUC(哪怕是在系统级别或数据库级别设置的),它都会覆盖索引上的存储参数设置。因此,为了避免难以排查的问题,我们建议仅使用一种方式来设置搜索参数:要么统一通过 GUC 设置,要么统一通过索引存储参数设置。
并非所有参数都支持此功能。有关支持此功能的全部参数,请参阅参考。
参考
索引存储参数(vchordrq)
vchordrq
probes
since v1.1.0
- 说明:
vchordrq.probes的回退值。 - 类型:整数列表(list of integers)
- 默认值:
epsilon
since v1.1.0
- 说明:
vchordrq.epsilon的回退值。 - 类型:实数(real)
- 默认值:
1.9 - 取值范围:
[0.0, 4.0]
maxsim_refine
since v1.1.0
- 说明:
vchordrq.maxsim_refine的回退值。 - 类型:整数(integer)
- 默认值:
0 - 取值范围:
[0, 2147483647]
maxsim_threshold
since v1.1.0
- 说明:
vchordrq.maxsim_threshold的回退值。 - 类型:整数(integer)
- 默认值:
0 - 取值范围:
[0, 2147483647]
索引存储参数(vchordg)
vchordg
ef_search
since v1.1.0
- 说明:
vchordg.ef_search的回退值。 - 类型:整数(integer)
- 默认值:
64 - 取值范围:
[1, 65535]
9 - 评估召回率
在向量搜索中,召回率(recall)是指索引返回的近似最近邻中真实最近邻所占的比例。例如,如果索引检索出 个近似最近邻,其中有 个属于真实最近邻,那么召回率就是 。
该功能目前处于预览阶段。
召回率评估
since v0.5.0召回率衡量的是搜索返回的相关结果占全部应返回真实结果的比例。在使用索引时,您通常希望了解不同参数配置所对应的召回率和 QPS。vchordrq_evaluate_query_recall 函数正是为评估召回率而设计的:它接收一条返回行标识符的查询语句,并返回对应的召回率。
vchordrq_evaluate_query_recall 函数内部依赖 ctid 将查询结果与基准真相(ground truth)进行比对。因此,传入的 query 参数必须以 SELECT ctid 开头。
默认情况下,该函数通过执行另一次高精度索引扫描来生成估算的基准真相,并据此评估召回率。如果您希望通过全表扫描(table scan)生成精确的基准真相来评估召回率,请将函数参数 exact_search 设置为 true。
查询采样
since v0.5.2为了方便评估生产或测试中的查询召回率,VectorChord 内置了查询采样方法。
假设您已经执行了一条示例向量搜索:SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 10。
在查询执行并被采样后,您可以检查捕获到的查询要素,包括模式名(schema)、索引名、表名、列名、操作符以及目标向量。
要测量已记录查询的平均召回率,您可以构造目标查询语句并调用评估函数:
如果您正在使用包含主库(primary)和备库(standby)的流复制(replication)功能,请注意:采样到的查询不会通过复制或备份进行同步。相反,每台服务器都会根据自己实际处理的查询独立维护各自的采样查询集合。
参考
vchordg 和多向量检索不支持此功能。
搜索参数(vchordrq)
vchordrq
vchordrq.query_sampling_enable
since v0.5.2
- 说明:启用或禁用
vchordrq索引扫描的查询采样。 - 类型:布尔值(boolean)
- 默认值:
off
vchordrq.query_sampling_max_records
since v0.5.2
- 说明:控制每个索引最多记录的查询数量。该设置仅在
vchordrq.query_sampling_enable开启时生效。 - 类型:整数(integer)
- 默认值:
0 - 取值范围:
[0, 10000] - 示例:
vchordrq.query_sampling_max_records = 0将为每个索引保留 0 条查询。vchordrq.query_sampling_max_records = 1000将为每个索引最多保留最近 1000 条查询。
vchordrq.query_sampling_rate
since v0.5.2
- 说明:控制记录查询的采样率。该设置仅在
vchordrq.query_sampling_enable开启时生效。 - 类型:实数(real)
- 默认值:
0 - 取值范围:
[0, 1] - 示例:
vchordrq.query_sampling_rate = 0不记录任何查询。vchordrq.query_sampling_rate = 0.01记录约 1% 的查询。vchordrq.query_sampling_rate = 1记录每一条查询(仅用于测试目的)。
函数(vchordrq)
vchordrq
vchordrq_sampled_queries
since v0.5.2
- 说明:检索指定
vchordrq索引已记录的查询集合。 - 返回值:
tableschema_name: text,查询中表的模式名index_name: text,查询中使用的索引名称table_name: text,查询中的表名column_name: text,参与索引扫描的列名operator: text,查询中使用的距离操作符value: text,查询中目标向量的文本表示
- 参数:
regclass,vchordrq索引的对象标识符
- 示例:
SELECT vchordrq_sampled_queries('items_embedding_idx');
vchordrq_sampled_values
since v0.5.2
- 说明:检索指定
vchordrq索引已记录查询中的目标向量。 - 返回值:
text,查询中目标向量的文本表示。 - 参数:
regclass,vchordrq索引的对象标识符
- 示例:
SELECT vchordrq_sampled_values('items_embedding_idx');
- 注意:该函数仅用于调试目的。
vchordrq_evaluate_query_recall
since v0.5.0
- 说明:评估给定
vchordrq查询的召回率。 - 返回值:
real - 参数:
query: text,待评估召回率的查询语句exact_search: boolean,是否使用精确基准真相代替估算的基准真相accu_probes: text,用于生成估算基准真相的vchordrq.probesaccu_epsilon: real,用于生成估算基准真相的vchordrq.epsilon
- 示例:
SELECT vchordrq_evaluate_query_recall(query => $$ SELECT ctid FROM t ORDER BY val <-> '[0.5, 0.25, 1.0]' LIMIT 10 $$);SELECT vchordrq_evaluate_query_recall(query => $$ SELECT ctid FROM t ORDER BY val <-> '[0.5, 0.25, 1.0]' LIMIT 10 $$, exact_search => true);SELECT vchordrq_evaluate_query_recall(query => $$ SELECT ctid FROM t ORDER BY val <-> '[0.5, 0.25, 1.0]' LIMIT 10 $$, accu_probes => '9999', accu_epsilon => 4.0);
视图(vchordrq)
vchordrq
vchordrq_sampled_queries
since v0.5.2
- 说明:检索所有
vchordrq索引已记录的查询集合。 - 示例:
SELECT * from vchordrq_sampled_queries;
- 注意:该视图仅用于调试目的。
10 - 预热
PostgreSQL 启动后,表和索引只有在首次被访问时才会从存储加载到缓冲池(buffer pool)中。随着查询的不断执行,索引中被频繁访问的部分会自然地缓存在缓冲池里。然而,在执行最初的几批查询时,由于缓冲池为空,查询速度往往较慢。
PostgreSQL 扩展 pg_prewarm 提供了 pg_prewarm 函数,可以在一定程度上解决这一问题。在 PostgreSQL 启动后,您只需向该函数传入索引名称,它就会将索引从存储加载到缓冲池中。
不过,这种方法只有在缓冲池远大于索引大小时才有效。对于向量索引而言,索引大小往往远超内存容量。向量索引在设计上将索引数据分为两部分:体量较小的热数据部分(hot portion)和体量庞大的冷数据部分(cold portion)。向量索引只需要将热数据部分保留在内存中即可获得理想的性能。因此,VectorChord 提供了专用的预热函数,仅将索引的热数据部分加载到内存中。
即使索引大小远大于内存容量,该函数依然能够很好地发挥作用。
参考
函数(vchordrq)
vchordrq
vchordrq_prewarm
- 说明:该函数通过将
vchordrq索引的热数据加载到缓冲池中来预热索引。 - 返回值:
text - 参数:
regclass,vchordrq索引的对象标识符
- 示例:
SELECT vchordrq_prewarm('items_embedding_idx')
函数(vchordg)
vchordg
vchordg_prewarm
since v0.5.0
- 说明:该函数通过将
vchordg索引的热数据加载到缓冲池中来预热索引。 - 返回值:
text - 参数:
regclass,vchordg索引的对象标识符
- 示例:
SELECT vchordg_prewarm('items_embedding_idx')
11 - 预过滤
在带过滤条件的索引扫描中,通常索引会先执行扫描,然后由 PostgreSQL 检查返回行是否满足过滤条件。在本页中,“过滤条件(filter)”是指 PostgreSQL 查询计划中索引扫描节点上的 Filter,它通常由 SQL 语句中的 WHERE 子句构造而成,并且不需要具备特定的形式。
然而,对于向量搜索而言,这种方式并不总是最高效的。请看下面这个查询:
为了检索出 个满足过滤条件的向量,索引大约返回了 行数据。这意味着索引执行了大量冗余计算。如果索引能够利用过滤条件进行剪枝,计算量将会大幅减少。为此,向量索引提供了 GUC 参数 vchordrq.prefilter,允许根据过滤条件对搜索空间进行剪枝。
预过滤(Prefilter)使向量索引能够在搜索过程中结合过滤条件进行判断,从而剪枝搜索空间;更小的搜索空间意味着更高的搜索效率。不过,检查过滤条件是否满足本身也会引入额外开销。因此,仅当过滤条件严格(能够排除大量行)且轻量(计算开销远低于计算向量距离)时,才推荐使用预过滤。为了便于理解,这里给出两个不适合使用预过滤的反例:
id % 97 > 0:该过滤条件过于宽松email ~ '^([a-zA-Z]+)*$':该过滤条件计算开销过高
根据我们的实验结果,在不同选择率(selectivity)下启用预过滤带来的 QPS 加速比如下:
- 在 1% 选择率下,QPS 提升 200%
- 在 10% 选择率下,QPS 提升 5%
参考
vchordg 不支持此功能。
搜索参数(vchordrq)
vchordrq
vchordrq.prefilter
since v0.4.0
- 说明:GUC 参数
vchordrq.prefilter允许在计算距离之前先对过滤条件进行求值。例如,在查询SELECT * FROM items WHERE id % 2 = 0 ORDER BY embedding <-> '[3,1,2]' LIMIT 5中,正常情况下索引会先计算所有候选行的embedding <-> '[3,1,2]'距离,然后将行传递给 PostgreSQL,由 PostgreSQL 过滤掉id % 2 != 0的行。启用该参数后,索引可以在计算距离之前预先评估过滤条件并丢弃不匹配的行,从而提高查询效率。 - 类型:布尔值(boolean)
- 默认值:
false
12 - 预取
预取(Prefetch)能够大幅加速涉及 I/O 操作的查询。它的工作原理是根据索引的实际访问需求,主动将缓冲区预读到操作系统页缓存(page cache)或 PostgreSQL 的缓冲池(buffer pool)中。
预取有助于将 I/O 与计算重叠执行,以及将不同的 I/O 操作相互重叠执行,从而显著减少查询执行期间等待缓冲区的时间,进而提升整体查询性能。
该功能默认启用。在 VectorChord 0.4.0 及更高版本中,任何查询都能从预取机制中受益。
在 0.4.0 版本之前,VectorChord 使用 PostgreSQL 的同步 I/O 接口。从 0.4.0 版本开始,VectorChord 在较新版本的 PostgreSQL 上使用异步 I/O 接口。VectorChord 所使用的 I/O 接口可以通过 GUC 参数进行控制。
在 GUC 参数中,同步 I/O 接口被称为 read_buffer,异步 I/O 接口被称为 read_stream,而 VectorChord 的模拟异步接口被称为 prefetch_buffer。
对于远小于内存大小的索引,这三种预取模式之间几乎没有差异。read_stream 可以加速所有基于磁盘的索引,但它仅在 PostgreSQL 17 和 18 上可用。为了解决这个问题,VectorChord 提供了一个名为 prefetch_buffer 的模拟异步 I/O 接口,在所有 PostgreSQL 版本上均可用,作为旧版本 PostgreSQL 的回退方案。prefetch_buffer 的效果略逊于 read_stream。
| 预取模式 | 说明 | 支持的 PostgreSQL 版本 |
|---|---|---|
read_stream |
PostgreSQL 中的异步 I/O 接口 | PostgreSQL 17, 18 |
prefetch_buffer |
VectorChord 的模拟异步 I/O 接口,在旧版本 PostgreSQL 上作为回退方案使用 | PostgreSQL 13, 14, 15, 16, 17, 18 |
read_buffer |
PostgreSQL 中的同步 I/O 接口 | PostgreSQL 13, 14, 15, 16, 17, 18 |
要在 PostgreSQL 18 上通过 read_stream 充分发挥此功能的威力,您需要启用异步 I/O。另请参阅 PostgreSQL 调优。
参考
搜索参数(vchordrq)
vchordrq
vchordrq.io_search
since v0.4.0
- 说明:该 GUC 参数控制在向量搜索中读取位向量(bit vector)时的 I/O 预取策略,会影响基于磁盘向量的搜索性能。
- 类型:字符串(string)
- 取值范围:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
{"read_buffer", "prefetch_buffer"} - PostgreSQL 17, 18:
{"read_buffer", "prefetch_buffer", "read_stream"}
- PostgreSQL 13, 14, 15, 16:
- 默认值:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
prefetch_buffer - PostgreSQL 17, 18:
read_stream
- PostgreSQL 13, 14, 15, 16:
- 注意:
read_buffer表示优先使用ReadBuffer。prefetch_buffer表示优先结合使用PrefetchBuffer和ReadBuffer。read_stream表示优先使用read_stream。
vchordrq.io_rerank
since v0.4.0
- 说明:该 GUC 参数控制在向量搜索中读取全精度向量时的 I/O 预取策略,会显著影响基于磁盘向量的搜索性能。
- 类型:字符串(string)
- 取值范围:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
{"read_buffer", "prefetch_buffer"} - PostgreSQL 17, 18:
{"read_buffer", "prefetch_buffer", "read_stream"}
- PostgreSQL 13, 14, 15, 16:
- 默认值:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
prefetch_buffer - PostgreSQL 17, 18:
read_stream
- PostgreSQL 13, 14, 15, 16:
- 注意:
read_buffer表示优先使用ReadBuffer。prefetch_buffer表示优先结合使用PrefetchBuffer和ReadBuffer。read_stream表示优先使用read_stream。
搜索参数(vchordg)
vchordg
vchordg.io_search
since v0.5.0
- 说明:该 GUC 参数控制在向量搜索中读取位向量(bit vector)时的 I/O 预取策略,会影响基于磁盘向量的搜索性能。
- 类型:字符串(string)
- 取值范围:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
{"read_buffer", "prefetch_buffer"} - PostgreSQL 17, 18:
{"read_buffer", "prefetch_buffer", "read_stream"}
- PostgreSQL 13, 14, 15, 16:
- 默认值:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
prefetch_buffer - PostgreSQL 17, 18:
read_stream
- PostgreSQL 13, 14, 15, 16:
- 注意:
read_buffer表示优先使用ReadBuffer。prefetch_buffer表示优先结合使用PrefetchBuffer和ReadBuffer。read_stream表示优先使用read_stream。
vchordg.io_rerank
since v0.5.0
- 说明:该 GUC 参数控制在向量搜索中读取全精度向量时的 I/O 预取策略,会显著影响基于磁盘向量的搜索性能。
- 类型:字符串(string)
- 取值范围:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
{"read_buffer", "prefetch_buffer"} - PostgreSQL 17, 18:
{"read_buffer", "prefetch_buffer", "read_stream"}
- PostgreSQL 13, 14, 15, 16:
- 默认值:取决于 PostgreSQL 版本
- PostgreSQL 13, 14, 15, 16:
prefetch_buffer - PostgreSQL 17, 18:
read_stream
- PostgreSQL 13, 14, 15, 16:
- 注意:
read_buffer表示优先使用ReadBuffer。prefetch_buffer表示优先结合使用PrefetchBuffer和ReadBuffer。read_stream表示优先使用read_stream。
13 - 表内重排序
PostgreSQL 索引会存储行标识符(row identifier)以及被索引列的值。通常情况下,被索引的列具有简单的数据类型,例如整数或长度有限的短字符串。
然而,如果被索引的列是向量类型,那么表和索引都会存储向量,这意味着您需要为这些向量支付双倍的存储成本。如果您希望降低这部分开销,可以在构建索引时启用 rerank_in_table。启用后,索引将采用一种巧妙的方式:它自身不再存储原始向量,而是在需要时直接从表中读取它们。
启用该选项会导致查询性能下降,并且在启用 rerank_in_table 时,预取(prefetch)功能将无法生效。
这是一种以时间换空间的折中方案,通常不推荐使用,仅适用于需要解决磁盘存储空间极度紧张的特定场景。
参考
vchordg 不支持此功能。
索引选项(vchordrq)
vchordrq
rerank_in_table
since v0.2.1
- 说明:该选项决定是否从表中读取向量进行重排序。如果启用,索引所需的存储空间将减少,但查询延迟会显著增加。仅当磁盘空间极其有限时才应启用此选项。
- 类型:布尔值(boolean)
- 默认值:
false - 示例:
rerank_in_table = false表示向量同时存储在索引和表中,搜索时从索引中读取向量。rerank_in_table = true表示向量仅存储在表中,搜索时从表中读取向量。
14 - 分区调优
对于大表而言,分区阶段主导了整个索引构建的时间与内存消耗。
分区阶段的时间复杂度为 ,内存消耗(以字节为单位)可以近似表示为 ,
其中:
- 是向量维度(如果设置了
build.internal.kmeans_dimension,则为该参数的值)。 - 是
build.internal.sampling_factor。 - 是
build.internal.lists[-1]。 - 是
build.internal.build_threads。 - 是
build.internal.kmeans_iterations。
本页介绍几种用于优化构建时间和内存消耗的调优技巧。
层次 K-means
层次 K-means(Hierarchical K-means)是一种比劳埃德算法(Lloyd’s algorithm)更快的聚类算法。应用该算法后,渐近时间复杂度将降低至 。作为折中,它会对搜索性能和召回率产生轻微的负面影响。
用法:
降低采样因子
降低采样因子(sampling factor)可以线性地改善构建时间和内存消耗。作为折中,它会对搜索性能和召回率产生中等程度的负面影响。
用法:
降维
降维功能使用快速约翰逊-林登施特劳斯变换(Fast Johnson–Lindenstrauss transform),将参与聚类的向量维度降低至原始维度以下,从而线性地改善构建时间和内存消耗。作为折中,它会对搜索性能和召回率产生轻微的负面影响。
用法:
参考配置
LAION 100M
数据集:
- 来源:https://sisap-challenges.github.io/2024/datasets/
- 度量方式:内积(Inner Product)
- 维度:768
- 向量数量:100,000,000
- 测试向量数量:1,000
测试机器(AWS EC2 i7i.4xlarge):
- CPU:Intel Xeon Platinum 8559C
- vCPU 数量:16
- 内存:128 GiB
- 存储:3750 GiB NVMe SSD
测试结果:
- 总构建时间:18 分钟
- 峰值内存:6 GiB
- 测试用例 1
- 参数
vchordrq.probes = '40,140'vchordrq.epsilon = 1.4
- QPS @ Top10:120
- Recall @ Top10:0.949
- 参数
DEEP 1B
数据集:
- 来源:https://big-ann-benchmarks.com/neurips21.html
- 度量方式:欧氏距离(Euclidean)
- 维度:96
- 向量数量:1,000,000,000
- 测试向量数量:10,000
测试机器(AWS EC2 i7ie.6xlarge):
- CPU:Intel Xeon Platinum 8559C
- vCPU 数量:24
- 内存:192 GiB
- 存储:7500 GiB NVMe SSD
测试结果:
- 总构建时间:107 分钟
- 峰值内存:64 GiB
- 测试用例 1
- 参数
vchordrq.probes = '40,250'
- QPS @ Top10:68
- Recall @ Top10:0.951
- 参数
15 - 外部构建
除了使用分区调优中的方法(以牺牲一定 QPS 和召回率为代价来缩短构建时间并降低内存消耗)之外,您还可以选择在其他机器或 GPU 上离线运行分区阶段。由于 vchordg 没有分区阶段,因此该特性不支持 vchordg。
假设表名为 t,待建索引的列名为 val。
具体而言,您需要先对待建索引的列进行采样。
基于这些样本,您可以将向量空间划分为多个 Voronoi 单元(Voronoi cells)。完成划分后,创建一张表并将分区质心插入其中。
随后可以使用以下语法创建索引:
表结构格式
存储分区信息的表必须严格遵循以下表结构(Schema):
- 第一列必须为
id,类型为integer,不可为 null(NOT NULL),且必须唯一(UNIQUE)。 - 第二列必须为
parent,类型为integer,允许为 null(根节点为NULL)。 - 第三列必须为
vector,类型为vector,不可为 null(NOT NULL)。目前尚不支持halfvec或其他类型。
在逻辑上,这些数据构成了一棵树。从根节点到每个叶子节点的距离(深度)必须完全相同。
如果上述任何属性未得到满足,系统都会报错。
分区示例
为了帮助您快速上手,下面给出一个使用 faiss 执行向量空间分区的最小代码示例。
这段代码会计算出不同层级上所有 Voronoi 单元的生成元(质心)。树中的父子关系可通过计算最短距离来确定,此处省略具体细节。您可以使用任何能够生成多级 Voronoi 单元的算法,例如球面 K-means(spherical K-means)、平衡 K-means(balanced K-means)或 GPU K-means。