使用矢量索引提高扩展性
随着数据集从数亿行增长到数百万行,快速检索成为一项硬性要求。 如果没有优化,相似性搜索会扫描整个表,这会增加延迟并损害用户体验。
矢量索引通过首先将数据库定向到最有希望的行来减少工作,因此查询返回速度更快。
Azure Database for PostgreSQL 通过pgvector和pg_diskann扩展,支持矢量索引。 在检索扩充生成(RAG)解决方案中,将每个项的嵌入存储在与其相关字段相同的行上的 向量列中 ,然后为该列编制索引。 该索引是 矢量索引。
小窍门
若要能够在灵活服务器上使用pgvector或pg_diskann,必须先在vector服务器参数azure.extensions中添加vector扩展。
为什么矢量索引很重要
速度是关键。 希望查询尽快返回结果。 想想一位回答有关策略的问题的人力资源助理。 对于 500 行数据,全表扫描可能没问题。 对于 500 万行数据,则不行。 索引可减少查询时扫描的数据量。 随着数据的增长,为了加快查询速度,索引会稍微牺牲一些存储空间和构建时间。 矢量索引 快速缩小搜索空间范围,以便快速检索相关行。
PostgreSQL 中的索引选项
PostgreSQL 支持多个近似近邻索引类型进行矢量搜索。 每个人都有自己的长处和弱点。 由 pgvector 扩展提供两个,通过 pg_diskann 扩展提供第三个:
IVFFlat(带平面压缩的倒排文件)- 由
pgvector扩展提供。 将向量分组到多个 列表中。 在查询时,它会选取最近的列表,并将查询仅与这些列表中的项进行比较。ivfflat.probes设置控制每个查询要检查的列表数量;探测次数越多,通常召回率越高,但会增加时间。 如果将探针的数量设置为列表的数量,则搜索将检查每个列表(通过索引进行精确搜索),并失去速度优势。 从默认值开始,如果缺少明显的匹配项,请在查询时进行调整ivfflat.probes。- 优点:快速生成、适度内存、可预测行为。
- 缺点:可以选择 列表的数量;大型分发更改可能需要重建。
HNSW (分层导航小型世界) - 由
pgvector扩展提供。 生成多层邻居图。 搜索从顶层开始,随着向下移动到最邻近节点附近的更密集层而逐渐缩小范围。 相比IVFFlat,它可以在类似的召回率下提供更好的查询速度,但它使用更多的内存,而且需要更长的时间才能构建。 没有训练步骤,因此即使在空表上也可以创建索引。 生成时密钥设置为 m 和 ef_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列中,并使用pgvector或pg_diskann对该列编制索引。 选择IVFFlat以获得简单、内存占用较少的构建;选择HNSW以提升内存中的查询性能;当数据量非常大或内存紧张时,请选择DiskANN。 在查询时,将运算符类与查询中使用的距离匹配,以便规划器可以使用索引。 从默认值开始,如果缺少明显的匹配项或速度需要改进,则对查询时间参数(ivfflat.probes,hnsw.ef_searchdiskann.l_value_is)进行小而测量的调整。 进行任何更改后,请运行相同的EXPLAIN (ANALYZE, VERBOSE, BUFFERS) 查询,并检查计划是否使用索引、执行时间是否下降、I/O 是否减少,且排名前列的结果仍然正确。