启用和使用 DiskANN 扩展(预览版)

DiskANN 是一种可扩展的最近的邻域搜索算法,可给出近似的结果,适用于任意规模的高效矢量搜索。 这一算法能够做到高召回率、每秒高查询和低查询延迟,甚至适用于包含数十亿项目的数据集。 这些特征使其成为处理大量数据的强大工具。

若要了解有关 DiskANN 的详细信息,请参阅 DiskANN:针对 Web 规模搜索和建议的矢量搜索

pg_diskann 扩展增加了对使用 DiskANN 进行高效矢量索引和搜索的支持。

启用pg_diskann

若要在 pg_diskann Azure Database for PostgreSQL 灵活服务器实例上使用扩展,需要在实例级别 允许该扩展 。 然后,需要在要使用扩展提供的功能的每个数据库 上创建扩展

重要

此预览功能仅适用于新部署的 Azure Database for PostgreSQL 灵活服务器实例。

由于pg_diskann依赖于vector扩展,您可以在同一数据库中允许创建vector扩展,然后运行以下命令:

CREATE EXTENSION IF NOT EXISTS pg_diskann;

或者可以跳过显式允许和创建 vector 扩展,并运行之前的命令,附加 CASCADE 子句。 该子句使 PostgreSQL 在它所依赖的扩展上隐式执行 CREATE EXTENSION。 为此,请运行以下命令:

CREATE EXTENSION IF NOT EXISTS pg_diskann CASCADE;

若要从当前连接的数据库中删除扩展,请运行以下命令:

DROP EXTENSION IF EXISTS pg_diskann;

使用 diskann 索引访问方法

安装扩展后,可以在包含矢量数据的表列上创建 diskann 索引。 例如,若要在 embedding 表的 demo 列上创建索引,请使用以下命令:

CREATE TABLE demo (
 id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
 embedding public.vector(3)
 -- other columns
);

-- insert dummy data
INSERT INTO demo (embedding) VALUES
('[1.0, 2.0, 3.0]'),
('[4.0, 5.0, 6.0]'),
('[7.0, 8.0, 9.0]');

-- create a diskann index by using Cosine distance operator
CREATE INDEX demo_embedding_diskann_idx ON demo USING diskann (embedding vector_cosine_ops)

创建索引后,可以运行查询来查找最近的邻居。

以下查询查找向量 [2.0, 3.0, 4.0] 最近的 5 个邻居:

SELECT id, embedding
FROM demo
ORDER BY embedding <=> '[2.0, 3.0, 4.0]'
LIMIT 5;

Postgres 会自动决定何时使用 DiskANN 索引。 如果它选择不在你希望使用索引的情况下使用索引,请执行以下命令:

-- Explicit Transcation block to force use for DiskANN index.

BEGIN;
SET LOCAL enable_seqscan TO OFF;
-- Similarity search queries
COMMIT;

重要

enable_seqscan 设置为关闭,如果还有其他方法可用,则它不鼓励规划器使用查询规划器的顺序扫描计划。 由于该命令已禁用 SET LOCAL ,因此该设置仅对当前事务生效。 COMMIT 或 ROLLBACK 之后,会话级别设置将再次生效。 请注意,如果查询涉及其他表,该设置也会阻止在所有表中使用顺序扫描。

使用量化高效缩放(预览版)

DiskANN 使用产品量化(PQ)大幅减少矢量的内存占用。 与其他量化技术不同,PQ 算法可以更有效地压缩矢量,显著提高性能。  使用 PQ 的 DiskANN 可以保留更多内存中的数据,减少访问速度较慢的存储的需求,以及在比较压缩矢量时使用更少的计算。 使用大量数据> (100 万行)时,这会产生更好的性能和显著的成本节省

若要获取产品量化(PQ)功能的访问权限, 请注册预览版

加快索引生成

建议通过几种方法来改进索引生成时间。

使用更多内存

若要加快索引的创建速度,可以增加在 Postgres 实例上为索引生成分配的内存。 可以通过参数指定 maintenance_work_mem 内存使用情况。

-- Set the parameters
SET maintenance_work_mem = '8GB'; -- Depending on your resources

然后, CREATE INDEX 命令根据可用资源使用指定的工作内存来生成索引。

CREATE INDEX demo_embedding_diskann_idx ON demo USING diskann (embedding vector_cosine_ops)

小提示

可以在索引生成期间纵向扩展内存资源以提高索引编制速度,然后在索引完成时缩减。

使用并行化

若要加快索引的创建速度,可以使用并行工作线程。 创建表时,可以通过 parallel_workers 语句的 CREATE TABLE 存储参数指定工作器的数量。 稍后可以使用 SET 语句的 ALTER TABLE 子句对其进行调整。

CREATE TABLE demo (
	id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
	embedding public.vector(3)
) WITH (parallel_workers = 4);
ALTER TABLE demo SET (parallel_workers = 8);

然后, CREATE INDEX 命令使用指定的并行辅助角色数(具体取决于可用资源)来生成索引。

CREATE INDEX demo_embedding_diskann_idx ON demo USING diskann (embedding vector_cosine_ops)

重要

领导进程不能参与并行索引生成。

如果要使用并行辅助角色创建索引,则还需要相应地设置max_parallel_workersmax_worker_processesmax_parallel_maintenance_workers参数。 有关这些参数的详细信息,请参阅 控制资源使用情况和异步行为的参数

可以在不同的粒度级别设置这些参数。 例如,若要在会话级别设置它们,可以运行以下语句:

-- Set the parameters
SET max_parallel_workers = 8;
SET max_worker_processes = 8; -- Note: Requires server restart
SET max_parallel_maintenance_workers = 4;

若要了解在 Azure Database for PostgreSQL 灵活服务器中配置这些参数的其他选项,请参阅 “配置服务器参数”。

注意

max_worker_processes参数要求服务器重启才能生效。

如果这些参数的配置和服务器上的可用资源不允许启动并行工作器,PostgreSQL 会自动回退以在非parallel 模式下创建索引。

配置参数

创建 diskann 索引时,可以指定各种参数来控制其行为。

索引参数

  • max_neighbors:图形中每个节点的最大边缘数(默认值为 32)。 较高的值可以提高召回率,达到特定点。
  • l_value_ib:索引生成期间搜索列表的大小(默认值为 100)。 较高的值会使生成速度变慢,但索引质量会更高。
  • product_quantized:启用产品量化(默认值为 true)。
  • pq_param_num_chunks:产品量化的区块数(默认值为 0)。 0 表示它根据嵌入维度自动确定。 建议使用原始嵌入维度的 1/3。
  • pq_param_training_samples:用于训练 PQ 中心表的矢量数(默认值为 0)。 0 表示它根据表大小自动确定。
CREATE INDEX demo_embedding_diskann_custom_idx ON demo USING diskann (embedding vector_cosine_ops)
WITH (
 max_neighbors = 48,
 l_value_ib = 100
 product_quantized=true, 
 pq_param_num_chunks = 0,
 pq_param_training_samples = 0 
 );

扩展参数

  • diskann.iterative_search:控制搜索行为。

    diskann.iterative_search的配置:

    • relaxed_order (默认值):让 diskann 以迭代方式按批 diskann.l_value_is搜索图形,直到生成所需的元组数(可能受 LIMIT 子句限制)。 可能导致结果无序。

    • strict_order:类似于 relaxed_order,让 diskann 以迭代方式搜索图形,直到生成所需的元组数。 但是,它可确保按距离排序的严格顺序返回结果。

    • off:使用非迭代搜索功能,这意味着它在一个步骤中尝试提取 diskann.l_value_is 元组。 无论与查询匹配的 diskann.l_value_is 子句或元组数如何,非迭代搜索只能为查询返回最多 LIMIT 个矢量。

    若要将搜索行为 strict_order 更改为当前会话中执行的所有查询,请运行以下语句:

    SET diskann.iterative_search TO 'strict_order';
    

    若要更改它,使其仅影响当前事务中执行的所有查询,请运行以下语句:

    BEGIN;
    SET LOCAL diskann.iterative_search TO 'strict_order';
    -- All your queries
    COMMIT;
    
  • diskann.l_value_is:用于索引扫描的 L 值(默认值为 100)。 增加该值可提高召回率,但可能会降低查询速度。

    若要将索引扫描的 L 值更改为 20,对于当前会话中执行的所有查询,请运行以下语句:

    SET diskann.l_value_is TO 20;
    

    若要更改它,使其仅影响当前事务中执行的所有查询,请运行以下语句:

    BEGIN;
    SET LOCAL diskann.l_value_is TO 20;
    -- All your queries
    COMMIT;
    
数据集大小(行) 参数类型 名字 建议的值
<1百万 索引生成 l_value_ib 100
<1百万 索引生成 max_neighbors 32
<1百万 查询时间 diskann.l_value_is 100
 
1M-50M 索引生成 l_value_ib 100
1M-50M 索引生成 max_neighbors 64
1M-50M 索引生成 product_quantized
1M-50M 查询时间 diskann.l_value_is 100
 
>50M 索引生成 l_value_ib 100
>50M 索引生成 max_neighbors 96
>50M 索引生成 product_quantized
>50M 查询时间 diskann.l_value_is 100

注意

这些参数可能因特定数据集和用例而异。 用户可能必须尝试不同的参数值,才能找到其特定方案的最佳设置。

CREATE INDEX 和 REINDEX 进度

使用 PostgreSQL 12 及更高版本,您可以利用 pg_stat_progress_create_index 来检查 CREATE INDEX 或 REINDEX 操作的进度。

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

若要详细了解 CREATE INDEX 或 REINDEX 作业可能涉及的阶段,请参阅 CREATE INDEX 阶段

选择索引访问函数

矢量类型允许对存储的向量执行三种类型的搜索。 需要为索引选择正确的访问函数,以便数据库在执行查询时可以考虑索引。

pg_diskann 支持以下距离运算符

  • vector_l2_ops<-> 欧几里得距离
  • vector_cosine_ops<=> 余弦距离
  • vector_ip_ops<#> 内部产品

故障排除

错误: : diskann index needs to be upgraded to version 2...

遇到此错误时,可以通过以下方法解决:

  • 在索引上执行 REINDEXREDINDEX CONCURRENTLY 语句。

  • 由于 REINDEX 可能需要很长时间,因此该扩展还提供一 upgrade_diskann_index()个名为的用户定义函数,该函数可尽可能更快地升级索引。

    若要升级索引,请运行以下语句:

    SELECT upgrade_diskann_index('demo_embedding_diskann_custom_idx');
    

    若要将数据库中的所有 diskann 索引升级到当前版本,请运行以下语句:

    SELECT upgrade_diskann_index(pg_class.oid)
    FROM pg_class
    JOIN pg_am ON (pg_class.relam = pg_am.oid)
    WHERE pg_am.amname = 'diskann';