向量数据库Showroom系列收官之作,用统一方法论对比pgvector、Chroma、Qdrant、Weaviate、Pinecone、Milvus六款产品的适用场景、性能上限和运维负担。
六篇之前,这个展厅向你许下了一个承诺:我们不会告诉你该选哪个数据库,而是给你一套方法,让你判断自己究竟该选什么。今天,展厅正式收官:一张表、一棵决策树、一份检查清单。
完整参展名单如下:🚙 pgvector、🛴 Chroma、🏎️ Qdrant、🚐 Weaviate、🚕 Pinecone、🚄 Milvus——分别对应旅行车、踏板车、两厢车、跨界车、出租车和火车。
有两点综合说明,表格里放不下:
开箱即用的 hybrid search:支持的有 Qdrant(dense + sparse,使用 RRF/DBSF 融合)、Weaviate(内置 BM25F)、Milvus(内置 BM25)。不支持的有 pgvector、Chroma。至于 Pinecone,你需要自行提供 sparse vectors(采用的是 Qdrant 的模式,而非 Weaviate/Milvus 内置 BM25 的模式)——在押注之前,先查文档确认当前的支持情况。
原生 multi-tenancy:Weaviate(tenant = shard)、Pinecone(namespaces)、Milvus(partition keys / 独立数据库)。pgvector 通过 RLS 实现;Qdrant 需要使用 payload filters;Chroma 不会替你搭好这些机制。
本系列反复使用的计算公式:100 万 × 1536 维 × 4 字节 ≈ 6 GB 原始向量数据——而 HNSW 图会存储每个向量的一份副本,因此,在选择实例规格之前,先把这个数字大致翻倍。
另外还有三个值得一提的选项,各用一句话说明:Elasticsearch/OpenSearch——如果你已经在用这套技术栈,而且向量需求不高,可以考虑。Redis——如果你需要亚毫秒级响应,而且已经在运行它,可以考虑。FAISS——它是引擎,不是整辆车:速度极快,但没有车库、没有服务、没有钥匙——你得围绕它自己把车造出来。
flowchart TD
START["You need vector search"] --> Q1{"Prototyping, learning,\nor a personal tool?"}
Q1 -- "Yes" --> CHROMA["🛴 Chroma"]
Q1 -- "No" --> Q2{"Data already in Postgres\nand under ~10M vectors?"}
Q2 -- "Yes" --> PGVECTOR["🚙 pgvector"]
Q2 -- "No" --> Q3{"Anyone on the team\nwants to own infrastructure?"}
Q3 -- "Yes — wants to own infra" --> Q4
Q3 -- "Must self-host (compliance)" --> Q4
Q4{"Hundreds of millions\nto billions of vectors?"}
Q4 -- "Yes" --> MILVUS["🚄 Milvus — or rent Zilliz Cloud"]
Q4 -- "No" --> Q5{"Many tenants, want\nvectorization + hybrid\nfrom the factory?"}
Q5 -- "Yes" --> WEAVIATE["🚐 Weaviate"]
Q5 -- "No" --> QDRANT["🏎️ Qdrant"]
关于这棵决策树,有两点需要坦诚说明:
还没用 Postgres?旅行车依然是一个选项——托管 Postgres 点一下就能开通(第 1 篇)。你不必先有车库。
数据不能离开你的安全边界,同时又没人愿意负责运维?这是招聘问题,不是数据库问题。展厅里的任何一辆车都解决不了——假装它们能解决,正是让你陷入两周折腾的开端。
从决策树里选出 2–3 个最终候选,而不是六个全测。然后:
所有候选都使用同一个 embedding model——这是从第 3 篇开始就坚持的公平比较原则。
使用真实数据:从你的产品中选取 1 万–5 万份真实文档,以及 50–100 条真实查询。用 Lorem ipsum 占位文本,测出来的也只是占位文本的表现。
按你实际要运行的部署规格来测试:如果生产环境使用集群,就按那个集群形态做 benchmark——这是 Milvus 给我们的教训。
加载数据之前,先创建 schema,以及 payload/metadata 索引——这是 Qdrant 和 Milvus 给我们的教训。
加载数据。然后等待:让 segments 稳定下来,让索引构建完成(benchmark 陷阱 #1 和 #2)。刚加载完数据就跑 benchmark,测到的是暴力搜索,而不是你的索引。
对照精确 ground truth,测量 Recall@10。自己对同一批向量做一次暴力搜索——5 万行数据只需要几秒钟:
import numpy as np
# vectors: (N, 1536) of your uploaded embeddings, query: (1536,)
# ground truth must use the SAME metric you query with —
# for cosine, normalize first
scores = vectors @ query
truth_indices = set(np.argsort(scores)[-10:][::-1]) # exact top-10 INDICES into `vectors`
# Option 1: if your DB returns positions in the same order you uploaded,
# compare indices directly
recall = len(truth_indices & set(returned_indices)) / 10
# Option 2: if your DB returns IDs, map indices to IDs first —
# the upload order defines the mapping, keep it consistent
# ids = ["doc_0", "doc_1", ...] # index i -> id, fixed at upload time
# truth_ids = {ids[i] for i in truth_indices}
# recall = len(truth_ids & set(returned_ids)) / 10
在符合实际业务的 QPS 下测量 p95 latency,而不是演示时的 QPS。
测量索引构建完成后的 RAM 占用——预计约为原始向量数据的 2 倍(本系列的经验法则)。
测试带过滤条件的搜索:“similar AND year=2024”能否返回 LIMIT 指定的完整数量?(第 1 篇的陷阱。)
插入后立即搜索:结果是否时好时坏?现在你就能发现,不必等到生产环境才知道(出租车和火车的问题)。
写入过程中杀掉进程(自行托管),或测试发生错误后的重试(托管服务)——然后检查是否有任何数据丢失。
把所有数据完整导出——用实际耗时多少小时来衡量退出成本,而不是听承诺。
托管服务(Pinecone):你的实际 QPS × top_k → read units → 美元。托管服务(Qdrant / Weaviate / Zilliz Cloud):RAM + CPU → 每月订阅费用。无论哪种方式,计费表都不会停。
自行托管:根据测得的 RAM 占用 → 选择能容纳它的最小实例 → × 12 个月。
填好评分卡,然后睡一觉,明天再做决定:
没错,“直觉”也是其中一列。DX 同样属于总拥有成本——每个工作日,你都要为它付出代价。
第二天早上:在冷启动状态下,重新执行同一批查询。只有预热后才好看的数字,是一项需要正视的发现,不能只当成脚注。
本系列的所有内容在撰写时都成立;数据库会不断演进——请对照文档核实版本。这套方法不会过时:按总拥有成本判断,用自己的数据验证,按当前规模选购,在达到新规模时重新评估。
部分评论可能仅对已登录的访客可见。登录后可查看所有评论。
如需采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。