优化向量查询性能

本文档介绍了如何调整索引,以提高查询性能并提升检索率。

调整 ScaNN 索引

ScaNN 索引使用基于树量化的编制索引。在树量化技术中,索引会学习搜索树以及量化(或哈希)函数。当您运行查询时,搜索树用于修剪搜索空间,而量化则用于压缩索引大小。这种剪枝可以加快计算查询向量与数据库向量之间的相似度(即距离)得分。

为了同时实现较高的每秒查询次数 (QPS) 和较高的最近邻查询召回率,您必须以最适合您的数据和查询的方式对 ScaNN 索引树进行分区。

在构建 ScaNN 索引之前,请完成以下操作:

  • 确保已创建包含数据的表。
  • 请确保您为 maintenance_work_memshared_buffers 标志设置的值小于机器总内存,以免在生成索引时出现问题。

调参参数

以下索引参数和数据库标志可搭配使用,以便找到检索率和 QPS 之间的适当平衡点。所有参数都适用于这两种 ScaNN 索引类型。

调优参数 说明 参数类型
num_leaves 要应用于此索引的分区数。创建索引时应用到的分区数量会影响索引性能。通过为固定数量的向量增加分区,您可以创建更精细的索引,从而提高召回率和查询性能。不过,代价是索引创建时间会延长。

由于三级树的构建速度比二级树快,因此您可以在创建三级树索引时增加 num_leaves_value,以便获得更好的性能。
  • 两级索引:将此值设置为介于 11048576 之间的任意值。

    如果您不确定如何选择确切值,请使用 sqrt(ROWS) 作为起点,其中 ROWS 是矢量行数。每个分区包含的矢量数量由
    ROWS/sqrt(ROWS) = sqrt(ROWS) 计算得出。

    由于可以在矢量行数少于 1,000 万的数据集上创建两级树索引,因此每个分区将包含的矢量数量少于 (sqrt(10M)),即 3200 个矢量。为了获得最佳性能,建议尽可能减少每个分区中的矢量数量。
  • 三级索引:将此值设置为介于 11048576 之间的任意值。

    如果您不确定如何选择确切值,请使用 power(ROWS, 2/3) 作为起点,其中 ROWS 是矢量行数。每个分区包含的矢量数量由
    ROWS/power(ROWS, 2/3) = power(ROWS, 1/3) 计算得出。

    由于三级树索引可在矢量行数超过 1 亿的数据集上创建,因此每个分区将包含超过
    (power(100M, 1/3)) 个矢量,即 465 个矢量。为了获得最佳性能,建议尽可能减少每个分区中的矢量数量。
索引创建
quantizer 您要为 K 均值树使用的量化器类型。默认值为 SQ8,可提高查询性能。

将其设为 FLAT 可提高检索率。
索引创建
enable_pca 启用主成分分析 (PCA),这是一种降维技术,用于尽可能自动缩减嵌入的大小。此选项默认处于启用状态。

如果您发现回想能力下降,请将其设为 false
索引创建
scann.num_leaves_to_search 数据库标志用于控制召回率和 QPS 之间的权衡。默认值为 num_leaves 中设置的值的 1%。

设置的值越高,召回率越高,但 QPS 越低,反之亦然。
查询运行时
scann.max_top_neighbors_buffer_size 数据库标志用于指定缓存大小,通过在内存(而非磁盘)中为扫描的候选邻居评分或对其进行排名,从而提高过滤查询的性能。默认值为 20000

设置的值越高,过滤查询的 QPS 就越高,但内存用量也会越高,反之亦然。
查询运行时
scann.pre_reordering_num_neighbors 设置数据库标志后,该标志会指定在初始搜索确定一组候选项后,重新排序阶段要考虑的候选邻居数量。将此值设置为高于您希望查询返回的相邻项数量的值。

值集越高,召回率越高,但这种方法会导致 QPS 较低。
查询运行时
max_num_levels K-means 聚类树的最大层数。
  • 两级树索引:默认设置为基于树的两级量化。
  • 三级树索引:对于基于三级树的量化,请明确设置为 2
索引创建

调整 ScaNN 索引

请考虑以下两个级别和三级 ScaNN 索引示例,它们展示了如何设置调整参数:

两级索引

SET LOCAL scann.num_leaves_to_search = 1;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 1/2)]);

三级索引

SET LOCAL scann.num_leaves_to_search = 10;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 2/3)], max_num_levels = 2);

对已生成 ScaNN 索引的表执行的任何插入或更新操作都会影响学习型树优化索引的方式。如果您的表容易频繁更新或插入,我们建议您定期重新编制现有 ScaNN 索引,以提高召回准确性。

您可以监控索引指标,以确定自构建索引以来创建的更改量,然后相应地重新编入索引。如需详细了解指标,请参阅矢量索引指标

调整方面的最佳实践

针对调整索引的建议因您计划使用的 ScaNN 索引类型而异。本部分提供了有关如何调整索引参数以在召回率和 QPS 之间取得最佳平衡的建议。

两级树索引

如需应用建议来帮助您为数据集找到 num_leavesnum_leaves_to_search 的最佳值,请按以下步骤操作:

  1. 创建 ScaNN 索引,并将 num_leaves 设置为已编入索引的表的行数的平方根。
  2. 运行测试查询,并增加 scann.num_of_leaves_to_search 的值,直到达到目标召回率范围(例如 95%)。如需详细了解如何分析查询,请参阅分析查询
  3. 记下 scann.num_leaves_to_searchnum_leaves 之间的比率,以备后用。此比率可提供关于数据集的近似值,有助于您实现目标召回率。

    如果您使用的是高维向量(500 个维度或更高),并且希望提高召回率,请尝试调整 scann.pre_reordering_num_neighbors 的值。作为起点,将该值设置为 100 * sqrt(K),其中 K 是您在查询中设置的限制。
  4. 如果查询达到目标召回率后 QPS 过低,请按以下步骤操作:
    1. 重新创建索引,并根据以下指南增加 num_leavesscann.num_leaves_to_search 的值:
      • num_leaves 设置为行数平方根的较大系数。例如,如果索引将 num_leaves 设置为行数的平方根,请尝试将其设置为平方根的两倍。如果该值已是双倍,请尝试将其设置为立方根的三倍。
      • 根据需要增加 scann.num_leaves_to_search,以保持其与 num_leaves(您在第 3 步中记录的值)的比率。
      • num_leaves 设置为小于或等于行数除以 100 的值。
    2. 再次运行测试查询。 在运行测试查询时,尝试降低 scann.num_leaves_to_search,找到一个值,使其能够提高 QPS 并保持较高的召回率。尝试使用不同的 scann.num_leaves_to_search 值,而无需重新构建索引。
  5. 重复第 4 步,直到 QPS 和召回率范围均达到可接受的值。

三级树索引

除了针对两级树 ScaNN 索引的建议之外,请使用以下指南和步骤来调整索引:

  • max_num_levels 从二级树的 1 增加到三级树的 2 会显著缩短创建索引的时间,但会降低召回率。使用以下建议设置 max_num_levels
    • 如果矢量行数超过 1 亿行,请将此值设置为 2
    • 如果矢量行数少于 1,000 万行,请将此值设为 1
    • 如果向量行数介于 1, 000 万到 1 亿行之间,请根据索引创建时间和所需的召回准确性设置为 12

如需应用建议以查找 num_leavesmax_num_levels 索引参数的最佳值,请按以下步骤操作:

  1. 根据您的数据集,使用以下 num_leavesmax_num_levels 组合创建 ScaNN 索引:

    • 矢量行数超过 1 亿行:将 max_num_levels 设置为 2,并将 num_leaves 设置为 power(rows, ⅔)
    • 向量行数少于 1 亿行:将 max_num_levels 设置为 1,将 num_leaves 设置为 sqrt(rows)
    • 矢量行数介于 1,000 万到 1 亿行之间:首先,将 max_num_levels 设置为 1,将 num_leaves 设置为 sqrt(rows)
  2. 运行测试查询。如需详细了解如何分析查询,请参阅分析查询

    如果索引创建时间令人满意,请保留 max_num_levels 值,并对 num_leaves 值进行实验,以获得最佳召回准确率。

  3. 如果您对索引创建时间不满意,请执行以下操作:

    • 如果 max_num_levels 值为 1,则删除该索引。将 max_num_levels 值设置为 2,然后重新构建索引。

      运行查询并调整 num_leaves 值,以实现最佳召回准确率。

    • 如果 max_num_levels 值为 2,则删除该索引。使用相同的 max_num_levels 值重新构建索引,并调整 num_leaves 值以实现最佳召回准确率。

调整 IVF 索引

调整为 listsivf.probesquantizer 参数设置的值可能会有助于优化应用的性能:

调优参数 说明 参数类型
lists 索引构建期间创建的列表数量。设置此值的起始点为 (rows)/1000(最多 100 万行),sqrt(rows)(超过 100 万行)。 索引创建
quantizer 您要为 K 均值树使用的量化器类型。默认值为 SQ8,可提升查询性能。将其设置为 FLAT 以提高回想率。 索引创建
ivf.probes 搜索期间要探索的最近列表的数量。此值的起始点为
sqrt(lists)
查询运行时

请考虑以下示例,其中显示了设置了调整参数的 IVF 索引:

SET LOCAL ivf.probes = 10;

CREATE INDEX my-ivf-index ON my-table
  USING ivf (vector_column cosine)
  WITH (lists = 100, quantizer = 'SQ8');

调整 IVFFlat 索引

调整为 listsivfflat.probes 参数设置的值有助于优化应用性能:

调优参数 说明 参数类型
lists 索引构建期间创建的列表数量。设置此值的起始点为 (rows)/1000(最多 100 万行),sqrt(rows)(超过 100 万行)。 索引创建
ivfflat.probes 搜索期间要探索的最近列表的数量。此值的起始点为
sqrt(lists)
查询运行时

在构建 IVFFlat 索引之前,请确保将数据库的 max_parallel_maintenance_workers 标志设置为足以加快对大型表的索引创建速度的值。

请考虑以下示例,其中显示了设置了调整参数的 IVFFlat 索引:

SET LOCAL ivfflat.probes = 10;

CREATE INDEX my-ivfflat-index ON my-table
  USING ivfflat (vector_column cosine)
  WITH (lists = 100);

调整 HNSW 索引

调整为 mef_constructionhnsw.ef_search 参数设置的值有助于优化应用性能。

调优参数 说明 参数类型
m 图中每个节点的连接数上限。您可以先将默认值设为 16(默认),然后根据数据集的大小尝试使用更高的值。 索引创建
ef_construction 图构建期间维护的动态候选列表的大小,该列表会不断更新节点的当前最佳最近邻候选项。将此值设置为高于 m 值的两倍的任何值,例如 64(默认值)。 索引创建
ef_search 搜索期间使用的动态候选名单的大小。您可以先将此值设置为 mef_construction,然后在观察召回率时进行更改。默认值为 40 查询运行时

请考虑以下示例,其中显示了设置了调整参数的 hnsw 索引:

SET LOCAL hnsw.ef_search = 40;

CREATE INDEX my-hnsw-index ON my-table
  USING hnsw (vector_column cosine)
  WITH (m = 16, ef_construction = 200);

分析查询

使用 EXPLAIN ANALYZE 命令分析查询数据分析,如以下 SQL 查询示例所示。

  EXPLAIN ANALYZE SELECT result-column FROM my-table
    ORDER BY EMBEDDING_COLUMN ::vector
    USING INDEX my-scann-index
    <-> embedding('textembedding-gecko@003', 'What is a database?')
    LIMIT 1;

示例响应 QUERY PLAN 包含所用时间、扫描或返回的行数以及使用的资源等信息。

Limit  (cost=0.42..15.27 rows=1 width=32) (actual time=0.106..0.132 rows=1 loops=1)
  ->  Index Scan using my-scann-index on my-table  (cost=0.42..858027.93 rows=100000 width=32) (actual time=0.105..0.129 rows=1 loops=1)
        Order By: (embedding_column <-> embedding('textgecko@003', 'What is a database?')::vector(768))
        Limit value: 1
Planning Time: 0.354 ms
Execution Time: 0.141 ms

查看向量索引指标

您可以使用向量索引指标来查看向量索引的性能、确定需要改进的方面,并根据需要根据指标调整索引。

如需查看所有矢量索引指标,请运行以下使用 pg_stat_ann_indexes 视图的 SQL 查询:

SELECT * FROM pg_stat_ann_indexes;

如需详细了解指标的完整列表,请参阅矢量索引指标

后续步骤