跳到主要内容

为生产环境选择向量数据库:基准测试不会告诉你的事

· 阅读需 13 分钟
Tian Pan
Software Engineer

当工程师评估向量数据库时,他们通常会加载 ANN 基准测试,并选择在 recall-at-10 排行榜上名列前茅的产品。三个月后,他们就开始提交迁移工单了。这些基准测试是在单一客户端、静态且索引完美的索引数据集上测量查询吞吐量的。但生产环境完全不是这样。

本指南涵盖了预测向量数据库在实际工作负载下能否撑住的五个维度,以及一个将这些维度与你的技术栈进行匹配的决策框架。

基准测试的问题

引用最广泛的向量数据库基准测试 —— ANN Benchmarks、VectorDBBench —— 都有一个根本性的缺陷:它们在数据导入后立即进行测试,此时索引已完全优化,且没有并发写入发生。而生产系统在查询运行的同时,会不断地插入、删除和更新向量。索引需要实时地进行持续重新优化,而这正是大多数供应商发生内存溢出 (OOM) 故障的隐秘之处。

一个相关的问题是:这些基准测试使用单一客户端进行测试。而生产环境意味着有 100 多个并发客户端同时点击不同的元数据子集。在并发情况下,延迟数据会彻底崩盘。

还有一个较少被讨论的法律问题。大约 30% 的主要向量数据库供应商在他们的 EULA(最终用户许可协议)中包含了 “DeWitt 条款” 规定,禁止客户在未经允许的情况下发布独立的基准测试。如果一个供应商的产品在实际环境下的表现很差,法律上你可能被禁止公开分享这一结果。

结论是:基准测试的排名与生产环境的结果关联性很弱。真正重要的维度是过滤准确性、更新延迟、租户隔离、混合搜索质量以及总体拥有成本 (TCO) —— 而大多数基准测试根本不测量这些指标。

过滤准确性:规模化下的隐形杀手

几乎每个生产环境的 RAG 系统都需要元数据过滤。你不是在搜索所有向量 —— 你是在搜索属于特定用户、文档类型或时间范围的向量。数据库如何处理这种过滤,比原始的召回率更具决定性。

通常有三种方法:

后过滤 (Post-filtering) 通过向量搜索检索候选集,然后丢弃不匹配的结果。对于低基数 (low-cardinality) 过滤器(比如过滤到 2% 的数据),你会丢弃 98% 的检索结果。你在浪费计算资源,浪费程度与过滤器的选择性成正比。

预过滤 (Pre-filtering) 先应用元数据过滤器,然后对剩余的子集运行向量搜索。这适用于大型、高基数的结果集,但对于较小的子集,它会破坏 HNSW 图的连通性,导致召回率崩塌。

可过滤的 HNSW (Filterable HNSW)(Qdrant 的方法)将过滤器集成到图遍历本身中。它为每个 payload 值创建子图,并合并回完整的图中,同时使用自适应查询规划:如果过滤器匹配很多点,它会执行标准的 HNSW 遍历并跳过不匹配的节点;如果过滤器具有高度选择性,它可能会完全跳过 HNSW 并进行直接扫描。其结果是在同等召回率下,查询速度比后过滤快 1.6 倍,且过滤字段的内存开销仅为 6% 左右。

Reddit 的生产部署非常有启发性。在管理超过 3.4 亿个向量时,他们发现随着并发用户的增加,元数据过滤(而非相似度计算)成为了主要的性能瓶颈。由于在向量图和关系型元数据存储之间移动数据带来的磁盘 I/O 开销, P99 延迟跃升了 10 倍。教训是:如果过滤是查询模式的核心,请在并发负载下对其进行明确评估。

更新延迟:新向量何时变得可搜索?

这个问题几乎从未出现在基准测试中,但对于推荐系统、实时库存搜索以及任何语料库频繁变化的应用程序来说至关重要。

不同的方法有显著差异:

  • 零延迟新鲜度 (Zero-delay freshness) (GaussDB-Vector):新插入的向量在提交后通过增量的磁盘索引更新立即生效。在十亿级数据集上可实现低于 50ms 的延迟和 95% 以上的召回率。
  • 基于 CDC 的最终一致性 (CDC-based eventual consistency) (Vespa,某些 Milvus 配置):写入操作追加到变更日志中,索引异步消耗该日志。适用于可以接受轻微延迟且吞吐量至上的场景。
  • 批量重新索引 (Bulk reindexing):在非高峰期导入并重建。仅适用于静态或变化缓慢的语料库。

对于大多数 RAG 流水线,最终一致性是可以接受的 —— 新文档在几秒钟内变得可搜索即可。但对于产品搜索或实时推荐,写入和搜索可见性之间的延迟直接影响用户体验。在假设新鲜度之前,请先明确你的候选数据库使用的是哪种模型。

多租户:三种方法,三种权衡

如果你正在构建多租户应用程序 —— 每个客户的数据必须在逻辑或物理上隔离 —— 那么你的租户隔离架构将决定后续的每一个性能和成本决策。

单租户索引 (Per-tenant indexes) 为每个租户提供独立的索引。搜索无需过滤,且索引结构可以针对每个租户的向量分布进行调整。成本:内存随租户数量线性增长,且当存在共享文档时,向量会在不同索引间重复。Weaviate 在 100,000 个集合(每个租户一个)上测试了这种模式,管理着超过 1 亿个向量 —— 这在可行性上没问题,但内存开销巨大。

带元数据过滤的共享索引 (Shared index with metadata filtering) 将所有向量放在一个带有租户标识符的索引中。内存效率高且灵活。成本:运行时权限检查会增加延迟,且对于具有异常向量分布的租户,共享索引结构的性能可能会很差。

命名空间 (Namespaces) (Pinecone 推荐的方法) 将索引划分为隔离的分段。比完整的单租户索引更简单,比单租户内存开销更便宜。成本:在租户数量巨大时,命名空间管理本身会变得具有操作复杂性。

正确的选择取决于你的隔离要求和规模。如果监管合规要求每个租户必须进行严格的数据隔离,那么无论成本如何,单租户索引可能都是不可避免的。如果隔离只是逻辑上的而非法律上的,那么共享索引过滤通常就足够了。

混合搜索质量:大规模场景下的稠密 + 稀疏检索

大多数生产环境下的搜索都受益于将语义(稠密向量)和关键词(稀疏 BM25)检索相结合。代码查询、命名实体、产品 SKU 以及技术术语都是纯语义相似度匹配失效的典型场景。

混合搜索并行运行两种检索方法,然后融合排序列表 —— 通常使用倒数排名融合(RRF):对于每个文档,它会在不同方法之间累加 1/(k + rank),其中 k 是一个平滑常数。alpha 参数则控制不同方法之间的权重。

在亿级规模(1 亿+ 向量)下,同时维护稀疏和稠密索引会增加存储和计算开销。融合的质量与单体检索的质量同样重要。Weaviate 支持两种融合算法,它们在质量和延迟之间有不同的取舍,并建议针对你的具体查询分布对两者进行评估。

乘积量化(PQ)通过将向量内存占用降低高达 20 倍,实现了十亿级数据集的单服务器部署 —— 你存储压缩后的向量用于候选检索,然后使用精确距离进行精排。在这种规模下,你只需要足够的精度来找到候选目标,而不需要对每一次比较都做到完美精度。

架构全对比:选择合适的架构

考虑到这些维度,主要的架构分类如下:

pgvector (PostgreSQL 扩展) 对于那些无法为运行一套独立系统提供充分理由的团队来说极具吸引力。在百万级规模下,Supabase 的基准测试显示 pgvector 的 HNSW 索引在 99% 的准确率下能够达到甚至超过专用数据库的性能。相比托管式向量数据库,60-80% 的成本节省是实实在在的,而且将向量和关系型数据保存在同一个具备 ACID 一致性的存储中,消除了一类一致性 Bug。实际限制:HNSW 索引运行在你的主数据库服务器上,会与应用程序查询竞争资源。随着数据集的增长,重度过滤的性能会下降。建议在达到 5000 万到 1 亿向量规模时规划迁移。

Pinecone 提供了零运维开销,这确实非常有价值。但在生产环境中的坑在于 P99 延迟:即使平均延迟看起来很正常,在内部扩缩容事件期间,尾部延迟会随机飙升 1-2 秒。其定价模型呈线性增长,缺乏规模效益 —— 在 1 亿向量规模下,每月的账单可能与你其余所有基础设施的费用相当。可观测性仅限于 Pinecone 在控制台中展示的内容;你无法访问分片级的指标或内部日志。

Qdrant 是当元数据过滤是你的核心工作负载时的最强选择。其可过滤的 HNSW 实现从算法层面解决了预过滤与后过滤的权衡问题。由于是自托管,运维负担确实存在,但随着过滤基数(cardinality)的变化,其过滤性能优势会进一步叠加。

Weaviate 通过 Prometheus 指标、仪表盘和追踪提供完整的可观测性 —— 你可以看到分片层级的资源争用。混合搜索实现得很好。权衡点在于:可观测性栈的搭建和维护由你负责。

Milvus 是需要分布式架构的 1 亿至 10 亿级规模部署的成熟选择。它运行在 Kafka, MinIO 和 etcd 之上 —— 这带来了显著的运维复杂性,需要 Kubernetes 专业知识。字节跳动、电商平台和基因组学公司的团队在这一规模的生产环境中运行 Milvus。当大规模自托管带来的成本节省超过基础设施投入时,这种运维负担就是合理的。

TurboPuffer (原生对象存储) 通过将向量存储在 S3 上并按需在缓存层级中拉取数据,其成本仅为托管替代方案的一小部分。它的 JIT 编译模型意味着随着数据被缓存,查询延迟会不断优化。令人意外的是 S3 请求成本:每天 100 万次 GET 请求大约会增加每月 12 美元的支出 —— 虽然可以接受,但那些只做查询计数压测而没有建模请求成本的团队往往发现得太晚。另一个担忧是:嵌入 (embeddings) 是源文本的有损压缩,这意味着 S3 中未加密的向量泄露的可还原数据可能比团队预期的更多。

Vespa 能很好地处理高频更新 —— 每节点高达每秒 10 万次写入并保持一致的新鲜度 —— 并且支持十亿级规模的复杂混合查询。它在 Web 应用场景中较少使用,但对于需要将文本排序信号与语义相似度相结合的搜索基础设施来说非常出色。

决策框架

与其看针锋相对的功能矩阵,更有用的框架是:核心约束条件是什么?

  • 向量少于 1000 万,团队拥有 PostgreSQL 经验:从 pgvector 开始。零新增基础设施,ACID 语义,熟悉的工具链。等到过滤性能或规模迫使你不得不讨论时再重新审视。
  • 过滤基数可变且不可预测:评估 Qdrant。当不同查询之间的过滤选择性差异很大时,可过滤 HNSW 的优势会更加明显。
  • 合规性要求数据保留在你的 VPC 内:仅限自托管 (Qdrant, Weaviate, Milvus)。Pinecone 和大多数托管服务都不予考虑。
  • 规模 > 1 亿,团队可以承担运维复杂性:Milvus 或 Vespa。大规模下的成本节省足以支撑对 Kubernetes 的投入。
  • 工作负载多变或有突发流量,成本是主要约束:TurboPuffer 或 pgvector。避免 Pinecone 的线性定价模型。
  • 必须零运维开销:Pinecone —— 但要针对你的并发工作负载进行专门的压测,并衡量 P99 而非 P50 延迟。

哪些东西容易过时

检索系统比模型层更容易成为负担。模型在不断进步且可以更换;而一个建立在私有格式之上、拥有 2 亿个嵌入向量(embeddings)的向量数据库会产生巨大的迁移阻力。

有三样东西会悄无声息地退化:

索引新鲜度信号:如果不对写入和可搜索性之间的延迟进行监控,生产系统就会逐渐倾向于提供过时的结果——这些结果看起来正确,但实际上并非如此。

Schema 变更下的过滤正确性:如果你的元数据 Schema 发生了演进(例如新增了字段或修改了枚举值),基于旧字段定义的索引可能会在不报错的情况下返回错误结果。

嵌入向量的兼容性:升级嵌入模型会使你的整个索引失效。当你更换嵌入模型时,所有主流向量数据库都需要进行完整的重新索引。那个今天看起来“运作良好”的检索系统,在未来的某个时刻必然隐藏着一次对延迟极其敏感的迁移事件。

这些都不是逃避构建的理由,但它们是你应当像对待主数据库一样,以同样的架构谨慎态度来对待向量基础设施的理由。检索层正在成为承重基础设施,那些及早意识到这一点的团队,能够避免日后在凌晨 3 点处理突发故障。

References:Let's stay in touch and Follow me for more thoughts and updates