基于 PostgreSQL 的向量搜索:内存和磁盘解决方案的比较分析
摘要: 本文探讨了PostgreSQL中三种向量数据库扩展(pgvector、pgvectorscale和VectorChord)的性能对比。实验基于500万768维向量数据集,测试了内存优化和磁盘受限场景下的查询速度、索引构建效率及插入性能。结果显示,VectorChord在高召回率(>95%)下查询性能最优,尤其在重复查询时表现突出,但索引构建复杂;pgvector易用性佳,适合简单场景
引言:现实世界的挑战
并非总是需要专门的矢量数据库。借助 PostgreSQL 的扩展,例如pgvector、pgvectorscale和VectorChord(源自pgvecto.rs),您无需额外的基础设施即可获得矢量搜索和关系型数据库的强大功能。但采用这些扩展并非易事。以下是真实用户的评价:
首次查询耗时 30 秒,重复查询则只需100 毫秒。问题在于 I/O 速度非常慢。
构建一个包含 1200 万行、75 维向量的索引需要 3 个小时,这正常吗?
在为 2 亿个 75 维向量构建索引 13 小时后,我注意到在索引构建进行到 30% 时内存使用量达到 100GB/124GB,服务器最终会崩溃。
我们遇到了一些挑战,特别是关于频繁索引更新的需求、吞吐量限制、缺乏并行索引扫描以及没有内置量化等方面。
本文将深入探讨这些问题,帮助您了解这些工具背后的“原理”和“功能”,从而做出明智的决策。让我们一起探索如何应对这些复杂情况,并选择最适合您需求的解决方案。
在向量数据库中,我们最看重的是什么?
在 PostgreSQL 中,` vindex` 是一个至关重要的内存区域,它作为数据页(包括表数据和索引)shared_buffers的主要缓存。通过在这个内存缓冲区中存储频繁访问的向量索引块,PostgreSQL 可以显著减少缓慢的磁盘 I/O。当查询需要数据时,PostgreSQL 首先会检查 `vindex` 。如果数据存在,则会快速检索,无需访问磁盘。如果数据不存在,则会从磁盘获取数据并加载到 `vindex` 中以供后续查询使用。要配置此功能,请在 psql 中使用 `vindex` 命令并重启服务器。
shared_buffersshared_buffersALTER SYSTEM SET shared_buffers = 'xGB'

假设表中有一些向量,索引构建完成后,就可以开始查询了。即使shared_buffersPostgreSQL 的内存配置足够,它仍然需要从磁盘读取索引first query。然而,对于某些向量repeated query,由于可以直接从索引中检索shared_buffers,因此速度会快得多。
虽然first query冷启动或索引更改后的性能很重要,但持续性能通常取决于索引中“热”部分(即频繁访问的部分)缓存到内存(shared_buffers)后查询的执行速度,从而模拟上述repeated query场景。因此,针对缓存性能进行优化对于许多实际应用至关重要。
在本篇博客中,我们将比较向量相似度搜索扩展在以下情况下的性能:
-
内存充足时的查询性能
-
当内存不足且磁盘成为瓶颈时,查询性能会受到影响。
我们还将讨论与指数构建相关的其他方面,例如:
-
Index build speed构建索引需要多长时间?如果我的实例中有多个核心,它能否利用所有核心来加快速度? -
Memory usage应该为索引创建预留多少内存?这是选择实例时最重要的考虑因素。 -
Disk usage在给定的磁盘空间内可以托管多少个向量?这也是选择使用不可扩展的 NVMe SSD(一种高性能固态硬盘)存储的实例时需要考虑的因素。 -
Ease of use我可以用简单的CREATE INDEX命令创建索引吗?优化查询性能难吗?
最后,我们将讨论insertion performance索引构建完成后的情况。如果用户有流式数据,会触发频繁的索引更新,那么这一点就非常重要。
实验装置
这里我们介绍以下所有实验中使用的硬件和数据集的相关信息。
| 内存中的向量 | 磁盘上的向量 | 指数构建 | 插入性能 | |
| 实例类型 | I4I Extra Large | C6ID Large |
I4I Extra Large | I4I Extra Large |
| 虚拟CPU | 4 | 2 | 4 | 4 |
| 贮存 | 937 GB NVMe 固态硬盘 | 118 GB NVMe 固态硬盘 | 937 GB NVMe 固态硬盘 | 937 GB NVMe 固态硬盘 |
| 记忆 | 32.0 GiB | 4.0 GiB | 32.0 GiB | 32.0 GiB |
| PostgreSQL共享缓冲区 | 24.0 GiB | 2.0 GiB | 1.0 GiB | 24.0 GiB |
| 插入行 | 5,000,000 | 5,000,000 | 5,000,000 | 索引创建后 100 |
| 距离度量 | L2距离 | L2距离 | / | / |
| 测试查询 | 10,000 | 10,000 | / | / |
下表列出了我们在这些实验中使用的所有扩展程序的版本:
| 版本 | 图像 | |
| 矢量和弦 | v0.2.2 | tensorchord/vchord-postgres:pg17-v0.2.2 |
| pgvector | v0.8.0 | tensorchord/vchord-postgres:pg17-v0.2.2 ( with a pgvector v0.8.0 installed) |
| pgvectorscale | v0.6.0 | timescale/timescaledb-ha:pg17.4-ts2.18.2-oss |
内存中的向量:适用于小规模数据
对于向量搜索系统,如果想要获得最佳性能,确保整个索引都缓存shared_buffers在内存优化实例中至关重要。在这种情况下,内存优化实例的大小shared_buffers最好足够容纳整个索引。

在我们的实验中,我们选择了一个中等规模的数据集,LAION-5m它包含 500 万个 768 维的向量。为了在 AWSLAION-5m上托管该数据集,我们可以使用配备 937GB NVMe SSD 和 32GB 内存的shared_buffersAWS 实例。I4I Extra Large
在这台机器上,我们将内存设置为 24GB,并分别测量了和 的shared_buffers性能。以下是、和 的性能测试结果。first queryrepeated queryrepeated querypgvectorpgvectorscaleVectorChord
以下图表展示了查询速度 (QPS) 和搜索质量 (Recall@10/100) 之间的权衡关系。Recall@10 衡量的是前 10 个搜索结果中真实最近邻所占的百分比。通常,更高的 QPS 会以更低的召回率作为代价,反之亦然。更优的系统可能在相同的 QPS 下实现更高的召回率,或者在相同的召回率水平下实现更高的 QPS。我们的目标通常是在保持较高召回率目标(例如 95%)的同时,最大化 QPS。

为了测量首次查询性能,我们shared_buffers在依次运行 10,000 次测试查询之前清空了缓存。虽然缓存初始为空,但同一测试运行中先前查询加载到缓存中的索引/数据页有时会被后续查询重用。这会导致运行期间的瞬时 QPS 增加。我们报告所有 10,000 次查询的平均QPS,以提供冷缓存条件下性能的代表性指标。

总之,VectorChord与其他扩展相比,在高召回率(例如,>95% Recall@10)下,始终能获得更高的 QPS。对于其他扩展,在特定情况下pgvectorscale表现更佳first query,但一旦数据被缓存,pgvector则性能将优于它。
磁盘上的向量:适用于大规模数据
有些时候情况有所不同。对于数十亿个向量,不可能在单台机器上用内存存储整个索引。当待读取向量的大小超过内存容量时,加速效果shared_buffers会大幅降低,大部分数据需要从磁盘读取。

虽然shared_buffers效率有所降低,但操作系统的页面缓存仍然可以提供一些缓存优势,尽管其性能通常低于shared_buffers。
为了说明这种情况,我们可以将程序部署LAION-5m在一台配置低得多的机器上,该机器C6ID Large只有4GB内存,并且磁盘空间设置shared_buffers为2GB。这种配置会确保内存不足,大多数查询将被迫访问磁盘。
在这种情况下,由于内存不足,不同扩展之间的性能差异first and repeated queries会小得多。因此,只需评估repeated query每个扩展的性能即可。

与以往一样,VectorChord在高召回率水平(例如,>95% Recall@10)下,始终能获得比其他扩展更高的 QPS,其次是pgvector具有pgvectorscale类似 QPS 的扩展。
指数构建经验
正如我们之前讨论的,用户也关注一些索引构建指标:索引构建时间、内存使用量、磁盘使用量和易用性。我们在CREATE INDEX上述实验的步骤中,针对Laion-5m数据集测量了这些指标:
-
Core utilization:索引创建过程中有效利用的可用 CPU 核心百分比。数值越高,并行性越好。 -
Index build time:SQL 命令构建索引所花费的时间。如果索引构建需要额外的步骤(VectorChord external build),也应将其包含在内。 -
Memory usage:索引构建期间的峰值shared_buffers内存使用量,包括分配给 PostgreSQL 的 1G 内存。 -
Disk usagePGDATA:索引构建完成后磁盘上的大小,包括向量和索引本身。
所有查询性能均在独立机器上进行外部构建后进行测量A10 GPU。如果您坚持使用同一I4I Extra Large实例进行外部构建,索引构建时间将增加到 50 分钟,但这仍然具有竞争力。
在这种情况下,我们将内存大小设置shared_buffers为 1GB,并记录了每个扩展的峰值内存使用量。K-means 聚类算法的迭代次数均VectorChord's internal build设置VectorChord's external build为25 次。为了方便使用,K-means 聚类算法的pgvector语法类似pgvectorscale,VectorChord's internal build只需一条 SQL 语句即可构建索引,而 K- means 聚类算法的语法则VectorChord's external build更为复杂。

在大多数方面,VectorChord's external build它都取得了领先地位,但这在一定程度上牺牲了易用性,因为开发人员需要在 PostgreSQL 之外实现额外的 K-means 聚类步骤并管理由此产生的质心。
如果您想从一个简单的CREATE INDEX命令入手,采用直接的方法,那么这VectorChord's external build可能不是最佳选择,您可以考虑其他扩展/方法:
-
选择
VectorChord's internal build加快索引构建速度 -
pgvector如果你的内存真的不够用,就选择这个选项。 -
选择
pgvectorscale降低磁盘需求或获得更大容量
插入性能:针对流式生成的数据
对于流式向量数据,插入速度可能成为一个新问题。在实时数据分析系统中,每秒写入的新向量数量可能非常高。每次向索引添加新向量时,索引本身都必须更新。这种功能通常被称为insertion performance……
因此,我们设计了一个实验来检验插入操作的效果。在为 500 万个向量构建索引后,我们依次向表中插入另外 10 万个随机生成的向量,并计算所有插入操作的总耗时。

如您所见,在数据插入方面,VectorChord(每秒插入 1565 条数据)的性能远优于 pgvector(每秒插入 246 条数据)和 pgvectorscale(每秒插入 107 条数据)。对于大多数工作负载而言,每秒插入 100 条数据已经绰绰有余。但是,如果您发现生产环境中需要持续插入大量向量(例如,每秒插入 500 条数据),则可能会出现问题。
概括
根据以上结果,下表展示了三个扩展程序在各个方面的差异。我们可以看到,主要优势VectorChord集中在性能方面,而pgvector和则在易用性和容量pgvectorscale方面各有优势。
如果您想了解更详细的差异,下表列出了这三个扩展程序在各个方面的区别。我们可以看到, 的主要优势VectorChord集中在性能方面,而pgvector和 则在易用性和容量pgvectorscale方面各有优势。
| pgvector | pgvectorscale | 矢量和弦 | |
| 易用性 | 简单的 | 简单,类似于 pgvector | 内部简单,外部困难,分两阶段构建 |
| 最大维度可以索引 | 2000 | 16000 | 16000 |
| 易于调校 | 简单的 | 中等难度,需要针对不同查询手动调整 query_rescore 参数 | 简单的 |
| 最高可实现回忆 | 底层 | 中档 | 顶级 |
| 重复查询的 QPS | 中档 | 底层 | 顶级 |
| 首次查询的 QPS | 中档 | 顶级 | 顶级 |
| 磁盘索引的 QPS | 中档 | 中档 | 顶级 |
| 指数构建速度 | 中端机型,支持多核心 | 低端机型,不支持多核处理器 | 顶级配置,支持多核处理器 |
| 峰值指数构建内存使用情况 | 2.1 GB | 15.2 GB | 1.1 GB(外部),9.8 GB(内部) |
| 磁盘总使用量 | 几乎最大 | 最小 | 最大的 |
想了解 VectorChord 与其他扩展的比较吗?在后续的博客文章中,我们将探讨 PostgreSQL 向量扩展在更多场景下的差异,例如bm25 混合搜索(结合传统关键词搜索和向量相似度搜索)以及过滤向量搜索。准备好使用 VectorChord 来满足您的向量搜索需求了吗?下载扩展程序,欢迎加入我们的Discord 社区,或发送邮件至support@tensorchord.ai联系我们。
更多推荐
所有评论(0)