为 pgvector 调优 PostgreSQL
与传统的事务性或分析查询相比,矢量搜索工作负载对 PostgreSQL 有不同的需求。 了解这些差异有助于优化配置参数,以优化 AI 应用程序的查询延迟、内存使用情况和计算效率。
注释
本单元中的代码示例演示 PostgreSQL 和 pgvector 的配置模式。 显示的参数值是用于调校的起点。 最佳设置取决于特定的工作负荷、数据集大小和硬件。 在将更改应用到生产环境之前,始终对测试环境中的更改进行基准测试。
Pgvector 计算和内存要求
矢量相似性搜索涉及计算查询向量和可能数百万个存储矢量之间的距离。 这种计算模式与传统数据库操作根本不同,后者基于索引列筛选行或基于键值连接表。
执行矢量相似性查询时,pgvector 必须计算查询向量和候选向量之间的距离。 对于 1536 维嵌入(与 OpenAI 模型通用),每个距离计算涉及 1,536 个浮点运算。 在没有索引的情况下搜索 100 万个向量每次查询需要执行超过 15 亿次浮点运算。 这三个距离函数具有不同的计算成本,这些计算成本根据数据特征和性能要求影响你的选择。
-
L2 (尤克利丹) 距离:
<->使用运算符并计算平方差之和的平方根。 这是计算成本最高的选项。 -
余弦距离: 使用
<=>运算符并度量向量之间的角度。 它在内部对向量进行标准化,这增加了计算量,但提供了对比例不变的相似性。 -
内积:使用
<#>运算符并计算点积。 这是最快的操作,但需要向量预先规范化以进行有意义的相似性比较。
对于建议引擎和语义搜索,余弦距离通常是首选的,因为它始终处理不同数量级的矢量。 如果嵌入已规范化(许多嵌入 API 返回规范化向量),则内部积提供等效的结果,且计算较少。
与传统数据类型相比,矢量列消耗大量存储。 存储为 float4 (单精度)的单个 1536 维矢量需要 6,144 字节,以及额外开销。 一个包含 100 万个产品嵌入的表,仅矢量列就需要大约 6 GB。 PostgreSQL 处理矢量查询时,它会将矢量数据加载到内存中。 可用内存与向量数据大小之间的关系直接影响查询是否可以在内存中高效执行,还是必须从磁盘重复读取。
更高维度的嵌入提供更多的语义分辨率,但同时也以平方方式增加存储和计算成本。 一个 3072 维向量(由一些较新的嵌入模型使用)需要进行四倍的距离计算,以及需要两倍于存储一个 1536 维向量的存储空间。 选择嵌入维度时,请考虑准确性要求。 对于许多建议和搜索应用程序,768 或 1,024 个维度提供了足够的质量,且资源消耗量明显降低。
矢量工作负荷的内存配置
PostgreSQL 的内存参数显著影响矢量查询性能。 适当的优化可确保矢量索引和经常访问的数据保留在内存中,从而减少昂贵的磁盘作。
参数 shared_buffers 控制 PostgreSQL 的共享内存缓存,其中经常访问的数据页驻留。 对于矢量工作负荷,此缓存应足够大,可以容纳矢量索引和热数据。 对于矢量密集型工作负荷,缓存命中率低于 99% 表示可能 shared_buffers 太小。 在 Azure Database for PostgreSQL 上,此参数会根据计算层自动调整,但可以在层的允许范围内对其进行调整。 对于专用矢量搜索工作负载,请确保 shared_buffers 的容量足够容纳矢量索引,并留有余量以存储其他缓存数据。 起始值是可用内存的 25%,并根据监控情况而增加。 以下查询可帮助你检查当前的设置和缓存性能。
-- Check current setting
SHOW shared_buffers;
-- View buffer cache hit ratio
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;
参数 work_mem 控制可用于单个查询操作的内存,例如排序和哈希连接。 矢量相似性查询,特别是那些将矢量搜索与筛选和排序相结合的查询,受益于足够的 work_mem优势。 对于必须按相似性对结果进行排序的矢量作,默认值 work_mem (通常为 4 MB)通常太小。 对于使用 SET work_mem = '256MB';大型结果集执行矢量搜索的会话或查询,可以增加此值。 对全局增加 work_mem 持谨慎态度,因为该设置适用于每个连接的每个操作,因此处理具有复杂查询的 100 个并发连接的服务器可能会消耗 100 × work_mem × 每个查询的操作数的内存。 对于矢量工作负荷,请考虑在特定查询的会话级别设置 work_mem ,而不是全局设置。
该 effective_cache_size 参数告知查询规划器有多少内存可用于缓存,包括 PostgreSQL 的 shared_buffers 和操作系统的文件缓存。 此设置不会分配内存,但会影响规划器选择使用索引扫描还是顺序扫描。 设置 effective_cache_size 为专用数据库服务器上系统内存总量的大约 75%。 较高的值鼓励规划器使用索引,这通常有利于矢量搜索。 在 Azure Database for PostgreSQL 上,会根据层自动配置此项。
矢量搜索的查询规划器设置
PostgreSQL 的查询规划器根据成本估算决定如何执行查询。 多个参数会影响这些估计值,并针对新式 SSD 存储对其进行优化可改进矢量查询规划。
该 random_page_cost 参数估计读取随机磁盘页相对于顺序页的成本。 默认值 4.0 反映了旋转磁盘特征,其中随机访问明显慢于顺序访问。 Azure Database for PostgreSQL 使用 SSD 存储,其中随机访问和顺序访问具有类似的性能。 降低 random_page_cost 到 1.1-1.5 可鼓励规划器更容易使用索引扫描,这有利于访问分散数据页的矢量搜索。 可以使用SET random_page_cost = 1.1;调整此设置。
此参数 effective_io_concurrency 告知 PostgreSQL 存储系统可以处理的并发磁盘 I/O作数。 较高的值使位图堆扫描能够并行预提取更多页面。 SSD 存储处理并发 I/O 良好,因此对于基于 SSD 的 Azure Database for PostgreSQL 实例,请设置为 effective_io_concurrency 200。 这提高了将矢量相似性与元数据筛选相结合的查询的性能。
parallel_tuple_cost 和 parallel_setup_cost 参数控制 PostgreSQL 何时使用并行查询执行。 向量操作可受益于并行处理,尤其是在大表的顺序扫描中。 较低的 parallel_tuple_cost (默认值 0.1)和 parallel_setup_cost (默认值 1000)值鼓励并行执行。 对于具有大型表的向量工作负荷,启用并行度可以显著减少不使用索引时的查询时间。 可以使用 SHOW parallel_tuple_cost;、SHOW parallel_setup_cost; 和 SHOW max_parallel_workers_per_gather; 检查您的当前并行设置。
配置特定于 pgvector 的参数
pgvector 扩展提供配置参数,用于控制基于索引的搜索的准确性-速度权衡。 这些参数对于优化矢量查询性能至关重要。
使用 IVFFlat 索引时,参数 ivfflat.probes 控制搜索每个查询的索引分区数(列表)。 较高的参数值会提升召回率(找到更多真实的最近邻),但会减慢查询速度。 这种权衡是IVFFlat算法性能调优的核心。 你需要在错过良好匹配的风险和搜索更多分区的成本之间取得平衡。 默认值 1 仅搜索最有希望的单个分区,这是快速的,但可能会错过存储在相邻分区中的相关结果。 对于缺少良好匹配会降低用户体验的推荐引擎,请先将 ivfflat.probes 设置为 lists 参数的 5-10%,并根据实际测量的召回率进行调整。
-- Configure IVFFlat search depth
SET ivfflat.probes = 10;
-- Execute vector search
SELECT id, name, embedding <=> $1 AS distance
FROM products
ORDER BY embedding <=> $1
LIMIT 10;
对于 HNSW 索引,参数 hnsw.ef_search 控制搜索期间动态候选列表的大小。 较大的值会探索图中更多内容,以速度为代价提高召回率。 与 IVFFlat 的离散分区不同,HNSW 的图形结构意味着此参数会影响算法在返回结果之前如何彻底探索邻居连接。 默认值 40 为许多工作负荷提供了合理的平衡。 对于高准确度要求(如查找精确的前 10 个匹配项),增加到 100-200。 对于可接受近似结果的延迟关键应用,值低至 20 也可以。 在执行矢量搜索之前,先配置hnsw.ef_search和SET hnsw.ef_search = 100;。 最佳值取决于准确性要求和延迟预算。 使用代表性查询进行基准测试,以便为应用程序找到适当的平衡。
监视和度量性能
没有测量的调优只是猜测。 使用 PostgreSQL 的内置工具和 Azure Monitor 了解查询行为并验证配置更改。
该 EXPLAIN ANALYZE 命令显示 PostgreSQL 如何执行查询并提供实际计时信息。 对于向量查询,这会显示是否使用索引以及所用的时间。 了解执行计划有助于确定性能不佳是否源于缺少索引、欠佳参数设置或数据分布问题。 在向量查询之前运行 EXPLAIN ANALYZE 以查看执行计划。 检查 Index Scan 使用 [index_name](表示正在使用向量索引)、Seq Scan(表示顺序扫描,大表较慢)、实际耗时值(显示执行时间分布)以及行数(帮助判断筛选是否有效)。 如果预期使用索引却出现顺序扫描,请检查查询的距离运算符是否与索引的运算符类匹配(例如,用 <=> 创建索引时使用的 vector_cosine_ops 运算符)。
有时 PostgreSQL 选择不使用可用索引。 向量查询常见原因包括返回表中大部分行的查询(索引开销超过顺序扫描)、数据大幅变化后统计信息过时,或距离运算符与索引运算符类不匹配。 运行 ANALYZE products; 以更新统计信息,以便进行准确的规划。 可以使用 SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'products'; 检查索引信息。
Azure Database for PostgreSQL 通过 Azure Monitor 公开指标,以帮助识别性能瓶颈。 监视 CPU 百分比(高的持续 CPU 使用率表示计算绑定矢量操作)、内存百分比(接近限制时,建议增加计算层级或优化查询)、存储 IO 百分比(高值表示数据并不适合缓存),以及活动连接(接近限制时表示连接池可能会有所帮助)。 为这些指标设置警报,以便在影响用户之前检测到性能下滑。
pgvector 调优最佳实践
有效的优化遵循系统的方法,而不是随机参数更改。
- 首先建立基线: 在进行更改之前测量查询延迟和资源使用情况。 如果没有基线,就无法判断更改是否有利或有害。
- 一次更改一个参数: 多个同时进行的更改使得无法对特定设置进行属性改进或回归。
- 使用类似生产的数据进行测试: 查询性能因数据大小和分布而显著变化。 对小型测试数据集进行微调通常会导致在大规模应用中失效的设置。
- 监视回归: 改善矢量搜索的参数可能会对其他工作负荷产生负面影响。 更改后监视整个系统运行状况。
- 记录设置: 记录所更改的内容、原因和效果。 在排查将来的问题时,本文档非常有用。