使用矢量索引提高扩展性

已完成

随着数据集从数亿行增长到数百万行,快速检索成为一项硬性要求。 如果没有优化,相似性搜索会扫描整个表,这会增加延迟并损害用户体验。 矢量索引通过首先将数据库定向到最有希望的行来减少工作,因此查询返回速度更快。 Azure Database for PostgreSQL 通过pgvectorpg_diskann扩展,支持矢量索引。 在检索扩充生成(RAG)解决方案中,将每个项的嵌入存储在与其相关字段相同的行上的 向量列中 ,然后为该列编制索引。 该索引是 矢量索引

小窍门

若要能够在灵活服务器上使用pgvectorpg_diskann,必须先在vector服务器参数azure.extensions中添加vector扩展。

为什么矢量索引很重要

速度是关键。 希望查询尽快返回结果。 想想一位回答有关策略的问题的人力资源助理。 对于 500 行数据,全表扫描可能没问题。 对于 500 万行数据,则不行。 索引可减少查询时扫描的数据量。 随着数据的增长,为了加快查询速度,索引会稍微牺牲一些存储空间和构建时间。 矢量索引 快速缩小搜索空间范围,以便快速检索相关行。

PostgreSQL 中的索引选项

PostgreSQL 支持多个近似近邻索引类型进行矢量搜索。 每个人都有自己的长处和弱点。 由 pgvector 扩展提供两个,通过 pg_diskann 扩展提供第三个:

展示矢量索引类型的案例使用场景的关系图。

  • IVFFlat(带平面压缩的倒排文件)- 由 pgvector 扩展提供。 将向量分组到多个 列表中。 在查询时,它会选取最近的列表,并将查询仅与这些列表中的项进行比较。 ivfflat.probes 设置控制每个查询要检查的列表数量;探测次数越多,通常召回率越高,但会增加时间。 如果将探针的数量设置为列表的数量,则搜索将检查每个列表(通过索引进行精确搜索),并失去速度优势。 从默认值开始,如果缺少明显的匹配项,请在查询时进行调整 ivfflat.probes

    • 优点:快速生成、适度内存、可预测行为。
    • 缺点:可以选择 列表的数量;大型分发更改可能需要重建。
  • HNSW (分层导航小型世界) - 由 pgvector 扩展提供。 生成多层邻居图。 搜索从顶层开始,随着向下移动到最邻近节点附近的更密集层而逐渐缩小范围。 相比IVFFlat,它可以在类似的召回率下提供更好的查询速度,但它使用更多的内存,而且需要更长的时间才能构建。 没有训练步骤,因此即使在空表上也可以创建索引。 生成时密钥设置为 mef_construction ,在查询时 hnsw.ef_search 。 该 m 参数设置每个层中每个节点的最大连接数(默认值 16)。 参数 ef_construction 设置生成索引时候选列表的大小(默认值 64)。 在查询时, hnsw.ef_search 控制搜索保留的候选列表(默认值 40)。 较大的值通常会提高召回率,但需要更多的内存或更长的构建/查询时间。 从默认值开始,然后在速度重要且结果已稳定时进行调整 hnsw.ef_search

    • 优点:查询时速度快、召回率高。
    • 缺点:更高的内存和更长的生成时间。
  • DiskANN(磁盘近似最近邻)- 由 pg_diskann 扩展提供。 此扩展添加单独的 DiskANN 索引访问方法。 将大部分结构保留在磁盘上,而在内存中仅使用一个小型工作集,专为超大数据量而设计。 即使对于拥有数十亿行数据的表,它也能提供高召回率、高每秒查询数和低查询延迟。 DiskANN 的 Azure 实现在 SSD 上存储完整矢量,同时在 RAM 中压缩工作集,从而剪裁内存使用并限制查询期间 SSD 读取。 随着数据的发展,内置的矢量压缩和量化保留了准确性,使 DiskANN 非常适合大型语义搜索和 RAG 方案。 默认值旨在在大规模环境中取得强有力的结果。 如果搜索跳过了合适的邻居,请增加 diskann.l_value_is 的值以考虑更多候选项,然后检查延迟。 如果非常大的表内存紧张,可以使用 product_quantized = true 创建索引以减少内存使用,但请注意可能会有轻微的质量权衡。

创建矢量索引

在 Azure Database for PostgreSQL 中创建索引很简单,首先启用相应的扩展,然后针对要使用的索引类型运行相应的 CREATE INDEX 语句。 假设你要为名为 company_policies 的表创建向量索引。 表嵌入存储在名为嵌入向量列中。

让我们创建一些索引:

创建 pgvector 索引:

  • 在 PostgreSQL 中创建扩展(只需为每个数据库启用一次扩展):

    CREATE EXTENSION IF NOT EXISTS vector;
    
  • 创建 IVFFlat 索引:

    CREATE INDEX company_policies_vec_ivf
    ON company_policies
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);
    
    ANALYZE company_policies;
    

    此语句在company_policies表的embedding列上创建IVFFlat索引。 该 lists 参数指定要在索引中创建的列表数。 可以根据数据大小和查询性能需求调整此值。

  • 创建 HNSW 索引:

    CREATE INDEX company_policies_vec_hnsw
    ON company_policies
    USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);
    

    此语句在company_policies表的embedding列上创建HNSW索引。 该 m 参数设置每个节点的最大连接数,并 ef_construction 控制索引构造过程中候选列表的大小。

创建 pg_diskann 索引:

  • pg_diskann创建索引(只需为每个数据库启用一次扩展):

    CREATE EXTENSION IF NOT EXISTS pg_diskann CASCADE;
    
  • 创建 DiskANN 索引。

    CREATE INDEX company_policies_vec_diskann
    ON company_policies
    USING diskann (embedding vector_cosine_ops);
    

    请注意索引的创建方式 DiskANN ,无需任何额外的参数。 虽然可以使用参数,但 DiskANN 设计为在各种用例中使用默认设置就能表现良好。

运算符和距离配对

创建向量索引时,必须将列上的运算符类与查询中的匹配运算符配对。 此匹配可确保数据库有效地使用索引。 最常见的距离指标包括:

  • 余弦距离 (<=> - 测量向量之间的角度。 单元规范化文本嵌入很常见。 使用 vector_cosine_ops 运算符类。
  • Euclidean/L2 距离 (<-> - 测量点之间的直线距离。 使用 vector_l2_ops 运算符类。
  • 内部产品 (<#> - 最大化点积。 最适合单位归一化矢量。 使用 vector_ip_ops 运算符类。

注释

经验法则: 使用与距离匹配的运算符类创建索引,并使用查询中的匹配运算符进行排序。 混合它们会阻止索引的使用。

在公司策略示例中,余弦距离适用于文本嵌入。 将每个问题和策略视为指向主题的箭头。 问题“我能休几天假?” 与“休假政策”的指向方式几乎相同(小角度→强匹配),而与“差旅报销政策”的指向相反(大角度→弱匹配)。 下面介绍如何使用余弦距离创建索引和查询:

-- Create index with cosine operator class
CREATE INDEX company_policies_vec_ivf_cos
ON company_policies USING ivfflat (embedding vector_cosine_ops);
  
-- Query using cosine distance operator
SELECT id, title
FROM company_policies
ORDER BY embedding <=> azure_openai.create_embeddings('<embedding-deployment>',
                                                    'How many vacation days do employees get?')::vector
LIMIT 5;

关键结论

随着表的增长,需要矢量索引,以便检索保持快速。 将每个嵌入存储在与其数据同一行的vector列中,并使用pgvectorpg_diskann对该列编制索引。 选择IVFFlat以获得简单、内存占用较少的构建;选择HNSW以提升内存中的查询性能;当数据量非常大或内存紧张时,请选择DiskANN。 在查询时,将运算符类与查询中使用的距离匹配,以便规划器可以使用索引。 从默认值开始,如果缺少明显的匹配项或速度需要改进,则对查询时间参数(ivfflat.probeshnsw.ef_searchdiskann.l_value_is)进行小而测量的调整。 进行任何更改后,请运行相同的EXPLAIN (ANALYZE, VERBOSE, BUFFERS) 查询,并检查计划是否使用索引、执行时间是否下降、I/O 是否减少,且排名前列的结果仍然正确。