Appearance
第11章 向量数据库:Qdrant、Milvus、pgvector 的架构取舍
"A vector database is a database that happens to support vectors well. Or, a vector algorithm wrapped in database concerns." — 2024 年后反复被引用的一句口水话
本章要点
- 向量数据库 = ANN 索引 + 存储/持久化/高可用 + 元数据过滤 + 分布式 + 运维工具链
- 三大主流方案各有定位:Qdrant(Rust 自包含、开发友好)、Milvus(云原生、亿级规模)、pgvector(PostgreSQL 扩展、复用现有 DB 栈)
- 选型五问题:规模、元数据复杂度、现有技术栈、部署形态、团队运维能力
- 多数中小 RAG 项目从 Qdrant 或 pgvector 起步;超大规模或有 K8s 运维能力的选 Milvus
- 云托管方案(Pinecone、Zilliz、Qdrant Cloud)适合追求"一键上手不运维"的团队
11.1 向量数据库是什么
第 10 章讨论的 HNSW/IVF/PQ 是算法。把算法落到生产可用的服务,需要补齐一系列工程能力:
- 持久化:索引重启后能加载、不用每次重建
- 高可用:单机宕机不影响服务
- 并发控制:读写并发时的一致性
- 元数据过滤:向量检索 + payload 字段过滤一体化
- 横向扩展:数据量超单机上限时自动分片
- 运维工具:监控、备份、灾难恢复
- SDK 和 API:多语言客户端、HTTP/gRPC 接口
向量数据库 = 这些工程能力的封装。直接用 FAISS 的代码跑 HNSW 能做 demo,生产上缺了上述能力每一项都是事故。
11.2 Qdrant:Rust 原生、开发友好
Qdrant(qdrant.tech)2021 年发布,Rust 编写。轻量、API 清晰、单机性能好——中小规模 RAG 项目的主流选择。
Qdrant 架构特点
- 单体服务:一个二进制,内置 HTTP 和 gRPC 接口
- 集合(collection):每个 collection 一个独立索引,类似传统 DB 的"表"
- 分片(shard):集合可分多个 shard 在多节点分布
- 副本(replica):每 shard 有多个副本实现高可用
- payload 索引:对 metadata 字段建 btree/keyword/geo 等二级索引、filter 下推高效
Qdrant 核心优势
- Rust 性能:单机 QPS 比 Python 系方案高 3-5 倍、p99 延迟稳定
- 部署简单:单二进制、一个配置文件、Docker 一行起来
- filter + vector 一体:向量查询和元数据过滤共享一次遍历——避免"先召回后过滤"的浪费
- API 直观:Python SDK 设计接近 ORM、上手 10 分钟
Qdrant 不适合
- 超大规模(10 亿 +)——分布式能力不如 Milvus 成熟
- 强 SQL 需求——只支持自己的查询语法、不是 SQL
- 现有 Postgres 栈不想另加组件——这种场景 pgvector 更合适
Qdrant 真实生产案例
- Anthropic 部分 RAG 系统用 Qdrant(公开技术博客提到)
- GitHub Copilot 的部分特性(公开演讲中 LangChain 作者提到)
- 许多中型 AI 创业公司的默认选择(LangChain / LlamaIndex 集成列表里排名靠前)
11.3 Milvus:云原生、亿级规模
Milvus(milvus.io)2019 年发布,Zilliz 开源,Go 实现。云原生 K8s 架构、分布式能力最强。
Milvus 架构特点
- 存算分离:存储层(MinIO/S3)、计算层(分布式 worker)独立扩展
- 多节点分角色:query node / data node / index node / root coord 等
- K8s 原生:Helm chart 一键部署、支持 Kubernetes operator
- 多种索引类型:Flat / HNSW / IVF-FLAT / IVF-PQ / SCANN / DiskANN 全支持
- 分布式事务:写入保证原子性、读写隔离
Milvus 核心优势
- 横向扩展到亿级:存算分离让 compute 和 storage 独立扩展、没有单机瓶颈
- 索引算法最全:几乎所有主流 ANN 都支持,不锁定单一算法
- 活跃生态:LangChain / LlamaIndex 一等公民支持、商业运营(Zilliz Cloud)稳定
Milvus 不适合
- 小规模部署——全套组件太重,百万级向量用 Milvus 是杀鸡用牛刀
- 没有 K8s 经验的团队——运维门槛高
- 追求最低延迟——分布式调度有固定 overhead(~5-10ms)
Milvus 架构图
Milvus 真实生产案例
- 阿里巴巴、腾讯等大厂的大规模向量检索(公开资料)
- Milvus 官方公布的用户列表里有 PayPal、IBM、NVIDIA、沃尔玛等
- 多数需要 10 亿级向量的 AI 产品(个性化推荐、图像搜索)
11.4 pgvector:PostgreSQL 扩展、复用 DB 栈
pgvector(github.com/pgvector/pgvector)是 PostgreSQL 的扩展,提供 vector 数据类型和距离运算符。2021 年发布,快速成为"已经在用 Postgres 就顺带做 RAG"的默认选择。
pgvector 架构特点
- Postgres 原生扩展:
CREATE EXTENSION vector;开箱即用 - SQL 查询:向量检索写在 SQL 里,和业务数据 JOIN 自然
- 事务 ACID:完整继承 Postgres 的事务保证
- HNSW + IVFFlat 索引:pgvector 0.5+ 支持 HNSW,0.7+ 支持 halfvec(半精度)
sql
-- 建表
CREATE TABLE chunks (
chunk_id TEXT PRIMARY KEY,
doc_id TEXT,
text TEXT,
embedding VECTOR(1024),
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 建 HNSW 索引
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 检索 + 过滤(标准 SQL)
SELECT chunk_id, text, 1 - (embedding <=> $1) AS similarity
FROM chunks
WHERE metadata->>'access_level' = 'internal'
AND created_at > NOW() - INTERVAL '90 days'
ORDER BY embedding <=> $1
LIMIT 20;pgvector 核心优势
- 零额外组件:用现有 Postgres 就够,不用多维护一套
- 事务完整:更新 chunk 的 text 和 embedding 在一个事务里、天然一致
- SQL 生态:能和业务表 JOIN,Postgres 工具链(备份、监控、ORM)全复用
- 便宜:云厂商的托管 Postgres 几乎都支持 pgvector,零额外成本
pgvector 不适合
- 超大规模(> 1 亿向量 / 库)——单机 Postgres 扛不住
- 高 QPS 写入 + 高 QPS 读取并存——HNSW 索引写入会慢
- 极致延迟要求——Postgres 层有事务和 WAL 开销,比专用向量库慢 2-3 倍
pgvector 核心取舍:性能 vs 简洁
同样 100 万 × 1024 向量的检索:
- Qdrant:P50 约 3ms、P99 约 10ms、QPS 上限约 5000
- pgvector:P50 约 8ms、P99 约 30ms、QPS 上限约 1500
pgvector 慢 2-3 倍,但对绝大多数 RAG 应用(单次查询 <20ms 就够)完全可接受。收益是复杂度降一个数量级——没有额外的服务要运维。
pgvector 真实生产案例
- Supabase 的 AI stack(大量中小公司 RAG 基础)
- Notion AI 的检索层(公开过使用 Postgres + pgvector)
- Neon、Crunchy Data 等 Postgres 云服务提供 pgvector 托管
11.5 选型五问题
问题 1:向量规模?
- < 100 万:Qdrant 或 pgvector 都行、pgvector 更省
- 100 万 - 1 亿:Qdrant 甜点区、Milvus 也可
- > 1 亿:Milvus 或分片 Qdrant
问题 2:元数据复杂度?
- 简单 (几个 enum 字段):三者都行
- 中等 (JSONB、多字段过滤):Qdrant 或 pgvector
- 复杂 (需要 JOIN 业务表):pgvector 独占优势
问题 3:现有技术栈?
- 已经重度用 Postgres:pgvector 顺手
- 已经用 K8s 和云原生栈:Milvus 适配
- 空白起步:Qdrant 最快上手
问题 4:部署形态?
- 单机 / Docker:Qdrant 或 pgvector
- K8s 集群:Milvus 或 Qdrant 集群
- Serverless / 托管:Pinecone、Zilliz Cloud、Qdrant Cloud
问题 5:团队运维能力?
- 没有专职 DBA:pgvector(Postgres 运维常识够用)或 Qdrant(简单)
- 有 K8s 专家:Milvus 可选
- 想零运维:托管方案
11.6 其他方案速览
Weaviate
Go 实现、支持向量 + 对象混合存储、GraphQL API。有独特的"向量 + 实体关系"模型,在知识图谱场景有优势。生态活跃但 API 设计偏向 GraphQL、对 SQL 习惯的团队有学习成本。
Elasticsearch
Elastic 8.0+ 支持 dense_vector 类型和 HNSW 索引。优点是和全文检索(BM25)天然一体——可以在同一个索引里做 hybrid search。缺点是 Elastic 本身资源占用高、大规模向量检索延迟不如专用库。
Redis / Valkey
Redis Stack 的 RediSearch 模块支持向量。优点是低延迟(内存数据库)、适合小规模高频查询。缺点是内存贵、不适合冷数据。
Pinecone
完全托管的向量数据库服务。优点是零运维、API 简单、扩展无感。缺点是数据不自主(在 Pinecone 的云)、成本可能高(按量计费到一定规模后贵于自托管)、国内访问延迟高。
LanceDB
基于 Apache Arrow 的 Rust 实现、lakehouse 风格。向量 + DataFrame 一体、和 Python ML 栈集成好。规模和生态目前小于 Qdrant/Milvus,但增长快。
选型小结
| 方案 | 典型规模 | 托管选项 | 特色 |
|---|---|---|---|
| Qdrant | 百万-千万 | Qdrant Cloud | Rust 性能、开发友好 |
| Milvus | 亿级 | Zilliz Cloud | 云原生、算法最全 |
| pgvector | 百万-千万 | 所有 Postgres 托管 | 复用 Postgres 栈 |
| Weaviate | 百万-千万 | Weaviate Cloud | GraphQL + 实体关系 |
| Elasticsearch | 百万-亿级 | Elastic Cloud | Hybrid search 原生 |
| Pinecone | 任意 | 只有托管 | 零运维、API 简单 |
| LanceDB | 百万-千万 | 自托管为主 | Arrow + Python ML |
11.7 生产运维的共性问题
无论选哪个方案,运维层面要处理的问题高度共性。
容量规划
估算存储和内存:
- 存储 = N × (d × 4 + metadata_size + overhead)
- HNSW 内存 ≈ 存储 × 1.3(图结构 overhead)
- IVF-PQ 内存 ≈ 存储 × 0.1-0.3(压缩受益)
500 万 × 1024 × 4 = 20 GB 原始向量 → HNSW 需约 26 GB RAM。内存预算决定能选 HNSW 还是 IVF-PQ。
备份和恢复
三种备份方式:
- 快照:向量库原生的 snapshot 接口(Qdrant、Milvus 都支持)。快、但格式私有
- 对象存储镜像:每小时 sync 索引文件到 S3。跨版本兼容好
- 业务 DB + 向量重建:备份只存原始 text + metadata、灾难时重新 embedding。最省存储、恢复最慢
生产推荐组合:快照日级(恢复快)+ 业务 DB 实时备份(兜底)。
监控指标
最小可观测集:
- QPS 和 P99 延迟:按请求类型(insert / search / filter)分开
- recall@k 回归:每天对 gold set 跑一次、告警低于阈值
- 索引大小和节点数:增长趋势、容量预警
- 内存 / 磁盘 / CPU:节点级别
- 错误率:按 error code 分桶
版本升级
主流向量库升级频率较高(季度 minor、半年 major)。升级要点:
- 索引格式向后兼容——新版能读旧版 index
- 先在 staging 跑完整流量一周、看 error rate 和 recall 变化
- 回滚预案:保留旧版 binary + 旧版 index 48 小时
- 迁移数据时用双写——老版本继续服务、新版本并行验证
11.8 自托管 vs 托管的成本模型
粗略估算(2026 年北美定价):
自托管 Qdrant(1 节点 + 1 备份):
- AWS EC2 m6i.xlarge × 2 = $280/月
- 存储 SSD 500GB = $50/月
- 备份 S3 100GB = $3/月
- 运维人力(0.2 FTE) = $3000/月
- 总计约 $3300/月
Qdrant Cloud 托管(同等规模):
- 按存储 + QPS 计费
- 约 $500-1500/月(按使用量)
Pinecone 托管:
- 按 pod 和 QPS 计费
- 约 $800-3000/月(按使用量)
pgvector on RDS:
- RDS db.m6i.xlarge = $250/月
- 存储 500 GB = $60/月
- 几乎零运维
- 总计约 $310/月(含 RDS 的 DBA 工具)
结论:小规模自托管只在"你本来就有运维团队"时省钱。新项目、追求 time-to-market 的场景,托管或 pgvector 更经济。大规模(> 亿级)自托管 Milvus 或 Qdrant 集群才开始省钱。
11.9 三款方案的真实对标 benchmark
2024-2025 年有若干公开 benchmark 对比向量数据库。归纳几个可靠的数字(来自 Qdrant 官方、ANN-Benchmarks、各公司技术博客——记得自己跑一次因为结果依赖数据分布):
100 万向量 × 1024 维
| 方案 | P50 QPS | P99 延迟 | recall@10 | 内存占用 |
|---|---|---|---|---|
| Qdrant | 4500 | 8ms | 0.96 | 5.5 GB |
| Milvus (standalone) | 3200 | 12ms | 0.96 | 6 GB |
| pgvector HNSW | 1400 | 25ms | 0.95 | 5 GB |
| Weaviate | 2800 | 15ms | 0.95 | 6 GB |
| Pinecone (托管 s1 pod) | 2000 | 18ms | 0.96 | N/A |
结论:Qdrant 在中小规模单机性能明显领先、pgvector 慢 2-3 倍但够用、Milvus 单机比不上 Qdrant(分布式才是优势)。
1 亿向量 × 1024 维
这个规模单机都跑不了。要分片:
- Qdrant 3 节点分片:P99 ~30ms、recall 0.94
- Milvus 8 节点集群:P99 ~40ms、recall 0.95
- pgvector:单机已经不够,要 Citus 分片、运维复杂
亿级规模 Milvus 的架构优势体现出来——存算分离让加节点变 query 能力、加存储变容量,线性可扩。Qdrant 的分片可行但运维经验少。
选型的定量启示
- 中小规模(< 千万)性能差异不决定性——选 API 最合手的
- 大规模(> 千万)Qdrant 和 Milvus 拉开差距——架构决定命运
- pgvector 一直够用但永远不是最快——"够用且简单"是它的杀手锏
11.10 向量检索 + 业务数据 JOIN 的取舍
一个常被低估的设计决策:检索结果返回后怎么和业务数据 JOIN?
模式 A:payload 里冗余业务字段
把 chunk 常用的业务字段(product_name、customer_id、price 等)冗余进向量库的 payload。检索时一次拿全。
- 优点:单次查询、延迟低
- 缺点:业务字段变了要同步更新 payload、数据一致性依赖应用层
模式 B:只存 chunk_id、JOIN 业务 DB
向量库只存 chunk_id、text、vector、必要的 filter 字段。检索回 chunk_ids 后去业务 DB JOIN。
- 优点:业务字段的变化和索引解耦、数据单一来源
- 缺点:多一跳、延迟 +10-30ms、下游 DB 是瓶颈
模式 C:pgvector 直接 JOIN
pgvector 让 chunk 和业务表在同一个 Postgres 里、一条 SQL 搞定。
- 优点:最一致、最简洁
- 缺点:需要 Postgres 栈、规模上限单机
生产选择:冗余频繁访问的字段(模式 A)+ JOIN 冷字段(模式 B)混合。或者用 pgvector 直接避开这个问题。第 17 章讨论引用溯源时会讨论这层架构。
11.11 向量库升级与迁移策略
选型时往往只想"怎么上线",不想"以后怎么换"。现实是:RAG 系统运行一两年后,几乎都会遇到一次迁移——数据规模超过原方案上限、成本压缩要换方案、原厂商停止维护、embedding 模型换代要全量重建。向量库的迁移和关系型 DB 迁移的区别:索引结构不通用,数据量大,且要保证在线检索不中断。
五种常见迁移场景
- 版本 minor 升级:比如 Qdrant 1.9 → 1.12。索引格式兼容、多数情况滚动重启即可
- 版本 major 升级:比如 Milvus 2.3 → 2.4。索引格式可能变、要重建索引或离线迁移
- 跨供应商迁移:Qdrant → Milvus 或反向。完全重建、没有共享的索引格式
- Embedding 模型换代:text-embedding-3-small → bge-m3。向量维度或语义空间变了、必须全量 re-embed
- 基础设施迁移:单机 → 集群、自托管 → 托管、跨云迁移(AWS → GCP)
五类场景的共同要求:迁移期间线上检索不能中断,recall 不能回退。
蓝绿索引:零停机迁移的标准动作
迁移期间线上不能断流量。蓝绿索引是最通用的做法:
- 蓝索引(当前线上)继续服务全部流量
- 绿索引(新版本 / 新向量库)在旁边离线建起来、全量灌入历史数据
- 增量同步期:把迁移开始后的所有新写入同时写入蓝和绿(业务层双写或用 CDC)
- Shadow 验证:把线上流量 shadow 复制一份到绿索引、对比召回集的 Jaccard 相似度、延迟分布
- 原子切换:通过配置中心或服务注册表把"当前索引"指针从蓝改绿、秒级生效
- 观察期:保留蓝索引 72 小时作为快速回滚。期间监控业务指标(点击率、答案满意度)
- 清理:观察期无异常后下线蓝索引
核心点:切换是原子的配置变更,不是数据拷贝——数据早就准备好了。
数据完整性验证
迁移结束前必须验证三件事:
- 数量一致:蓝绿两库的
count(*)完全相等 - 抽样对比:随机选 N 个 chunk_id、在两库分别查、向量和元数据逐位对比
- 召回一致:在 gold set 上跑 recall@10,新索引不能低于旧索引的 99%
只看"数量一致"是危险的——有过事故:迁移脚本把 payload 里的 created_at 字段丢了、chunk 数一致但下游时间过滤完全失效、业务线上跑了两天才发现。
Embedding 换代的特殊处理
换 embedding 模型是最贵的迁移——不是搬数据而是全量重新计算向量。工程要点:
- 离线计算:新模型的 embedding 在离线批处理里算完、不占线上资源
- 成本预估:N 个 chunk × 每 chunk embedding 成本、大 RAG 系统常常是几万到几十万美元
- 灰度切换:先对 10% 流量用新模型检索、A/B 看指标、再逐步扩大
- 不可回滚的决策:一旦新索引全量使用,旧向量可保留但不再更新——新旧向量空间不通用、无法混用
常见陷阱
- 忘了 payload 兼容性:新版本字段名或类型变了、应用层没跟上、线上 filter 失效
- 没给重建足够时间:亿级向量 HNSW 重建可能 24-48 小时,留给 staging 的时间必须包含这段
- 只测 recall 不测延迟:新索引 recall 一样但延迟多了 20ms,累加到端到端后体验下降
- 回滚路径没演练:切回蓝索引要有明确 runbook,不能等出事才写
迁移不是一次性工程问题——把**"未来会换"写进初始架构**(shard 边界、版本标签、双写能力)才是成熟团队的做法。
11.12 多租户架构:三款主流向量库的实现对比
企业级 RAG 几乎都是多租户——SaaS 服务多个客户、集团服务多个子公司、内部工具分部门/项目。每个租户的数据绝对不能被其他租户检索到。向量库的多租户怎么实现直接决定了架构上限。
三种多租户架构模式
- 每租户独立索引:一个 tenant 一个 collection(Qdrant / Milvus)或一张表(pgvector)。强隔离、最安全、但租户数万级时 overhead 崩溃(每 collection 都有固定资源开销)
- 共享索引 + filter:所有租户同一个 collection、查询时加
tenant_id = Xfilter。扩展性最好、但 filter 性能依赖 ch10 §10.11 讨论的下推实现 - 按租户分区:DB 级分区(Milvus partition_key)、物理上按 tenant 切片、查询时路由到对应分区
三款 DB 的原生支持对比
Qdrant:
- 原生支持 collection、适合 per-tenant 架构(数百租户以内)
- 1.7+ 提供
group_id机制、在单个 collection 里按 tenant 分桶、filter-aware 搜索优化 - payload index 对
tenant_id字段建 keyword index 后、filter 几乎零代价 - 万级租户:推荐共享 collection + group_id;百级租户:per-tenant collection
Milvus:
- Milvus 2.2+ 引入 partition_key——创建 collection 时指定
tenant_id为 partition_key、自动按其 hash 分区 - 查询时带 partition_key 值的自动路由到对应物理分区、非该租户的分区完全不扫
- 这是三款里最成熟的多租户方案——Zilliz Cloud 商业版的主力场景
- 劣势:partition_key 一旦设定不能改、要提前规划
pgvector:
- 复用 Postgres 的多租户模式:schema-per-tenant(每个租户一个 schema)、row-level security (RLS)(共享表 + 策略过滤)、或 database-per-tenant
- RLS 最常用——
CREATE POLICY tenant_isolation ON chunks USING (tenant_id = current_setting('app.tenant'))——应用设置 session 变量即可 - 优势:继承 Postgres 的 ACID 事务跨租户数据修改一致
- 劣势:索引是全局的、大租户的热点 chunk 挤占 shared buffer
选型决策矩阵
| 场景 | 推荐 | 原因 |
|---|---|---|
| < 50 租户、强隔离需求 | 任一:per-tenant collection/schema | 资源占用可忍、隔离最干净 |
| 100-1000 租户 | Qdrant group_id 或 Milvus partition_key | 扩展性 + 性能 |
| > 1 万租户 | Milvus partition_key 或自定义分片 | 物理分区是唯一路 |
| 已有 Postgres 栈 + 中小规模 | pgvector + RLS | 继承现有多租户运维 |
| 跨租户分析查询多 | pgvector | SQL JOIN 跨 tenant 天然支持 |
租户不均的热点问题
真实世界的多租户从不均匀——典型 "2% 头部租户占 80% 数据和查询量"。这在每种架构下都有不同表现:
- per-tenant collection:大租户的 collection 巨大、小租户几乎空——资源利用率差
- 共享 + filter:大租户的查询延迟拖慢整个 collection——邻居效应
- partition_key 分区:大租户的分区过大、小租户分区碎片——需要手动 rebalance
成熟方案:分层部署——大租户独立 collection/DB、中小租户共享。这需要 routing 层识别租户规模、动态选架构。
跨租户查询的例外
多租户不是绝对隔离——有合法场景需要跨租户:
- 平台管理员的全局搜索(运维、合规审查)
- 知识共享(集团公司内的子公司互相引用知识)
- benchmark 对比(对比不同租户的使用模式)
这些跨租户查询要另开一个权限等级——默认查询严格按 tenant 隔离、管理员 API 带明确 admin_access: true 参数才能跨。日志里这种查询必须标记、每次都审计(ch22 §22.14)。
多租户的常见坑
- 查询忘带 tenant_id:开发忘了、线上查到别的租户数据——合规事故。解决:ORM 层强制注入、测试里加跨租户污染测试
- index 不按 tenant 分区:HNSW 全局一张图、一个租户的查询走遍其他租户的图节点——延迟浪费。解决:用有租户感知的 DB(Milvus / Qdrant group_id)
- 认证 vs 授权混淆:认证知道是 tenant A 的用户、但查询时权限没下推到 DB——仍然查到 tenant B。解决:认证 → 生成 tenant_id → 查询层自动加 filter、应用层不用手写
- 租户迁移难:某客户从独立 collection 合并到共享 collection(或反向)——数据 copy + reindex 成本高。设计初期就选对架构
多租户是向量库选型里往往被低估的维度——上线 3-6 个月后才发现"当初选的架构扛不住"已经晚了。选型时就问清楚租户未来 2 年的数量级、按上限选架构。
11.13 向量库压测的方法与自建基准
§11.9 给了三款向量库的公开 benchmark 数字——但这些数字永远不代表你的业务。向量库的性能高度依赖你的向量分布、你的 filter 模式、你的 QPS 特点。生产选型必须自己跑一次压测、不能直接信别人的数字。但压测本身是一门手艺、没做好也会选错。
为什么别人的 benchmark 不可信
公开 benchmark 的典型局限:
- 数据集不代表你的业务:ANN-benchmarks 用 SIFT-1M / GIST-1M 这类学术数据、分布和你的 embedding 不同
- Query 模式不真实:用均匀随机 query、但真实流量里有热点、长尾、时间相关性
- 没有 filter:多数 benchmark 是纯 k-NN、不带 metadata filter——但你 99% 的生产查询都带 filter(ch10 §10.11)
- 硬件不同:别人在 AWS m6i.xlarge 跑、你用 Azure E8s——绝对数字差 2-3×
- 时间过时:向量库每半年一个大版本、六个月前的 benchmark 已经无效
结论:自己跑、用自己的数据、自己的查询、自己的硬件。这是选型前的硬投入、省不了。
压测数据集的三种选择
- 生产快照:从线上取一份脱敏样本(10-100 万 chunk)+ 过去一周 query log。最真实、但要处理合规和脱敏
- 合成数据集:用 LLM 生成业务相似的文档、embed 之、生成查询变体。规模灵活、但分布和真实略有偏差
- 公开数据集:MS-MARCO、FIQA、HotpotQA 等。便于外部对比、但偏离业务
实操:生产快照优先、合成数据补规模。10 万真实数据比 1000 万合成数据信息量大。
压测的五个维度
向量库压测不是只看"QPS"——至少五维度看齐:
| 维度 | 指标 | 工具支持 |
|---|---|---|
| 索引构建 | 构建时间、内存峰值 | 记录 |
| 检索精度 | recall@10 / @50、MRR | 和 Flat 对比 |
| 检索吞吐 | QPS at 不同并发数 | 压测工具 |
| 检索延迟 | P50/P99/P99.9 | 压测工具 |
| Filter 性能 | 带 filter 的 recall/QPS/延迟 | 专门跑 |
只看 QPS 不看 recall——可能是"快但不准"。只看延迟不看 filter——可能真实场景延迟翻倍。
压测工具链
2024-2026 年的主流压测工具:
- VectorDBBench(zilliztech/VectorDBBench):Zilliz 开源、支持 Qdrant / Milvus / Weaviate / pgvector / Pinecone、一套脚本跑多库对比。生产选型必用
- ANN-Benchmarks(erikbern/ann-benchmarks):学术标杆、覆盖更多算法、但数据集偏学术
- 自写脚本:最灵活、能精确贴合业务。每个认真做 RAG 的团队都写过一版
推荐组合:VectorDBBench 做初筛(跑一遍看哪些库明显不行)、自写脚本做深度压测(用生产数据、跟业务贴合)。
压测的正确打开方式
压测最容易犯的错:
- 数据集太小:10 万 chunk 什么库都快、看不出差异。至少 100 万起
- 一次性灌完就压:实际生产是增量写 + 持续读、要压mixed workload
- 没有预热:冷启动 QPS 低 50%——前几分钟数据废弃、跑稳态
- 硬件不稳定:公共云的 EC2 性能波动 20-30%、跑至少 3 次取平均
- 只测峰值 QPS:生产要看 sustained QPS(持续 1 小时不掉)——峰值能跑但持续不能的库不能用
- 忽略内存 / 磁盘:跑完看峰值内存、和 RAM 比——超了就 swap、线上会崩
压测报告的结构
典型压测报告应该包含:
text
## 向量库选型压测报告 2026-04
### 数据集
- 数据源:生产脱敏样本 120 万 chunk × 1024 dim
- Query: 线上一周 query log 抽 10 万条
- 硬件:AWS m6i.2xlarge (8vCPU / 32GB RAM)
### 索引构建
| 方案 | 构建时间 | 峰值内存 |
| Qdrant HNSW | 45 min | 18 GB |
| Milvus HNSW | 55 min | 22 GB |
| pgvector HNSW | 120 min | 14 GB |
### 检索性能(recall@10 = 95% 锁定)
| 方案 | QPS (8 并发) | P99 延迟 | 无 filter | 带 tenant filter |
| Qdrant | 4200 | 12ms | 0.96 | 0.95 |
| Milvus | 3100 | 16ms | 0.96 | 0.95 |
| pgvector | 1350 | 28ms | 0.95 | 0.94 |
### 结论
推荐 Qdrant——QPS 和延迟综合最佳、内存开销合理。带方法 + 数据 + 结论——缺任一项报告不值得信。
压测的隐藏成本
自建压测不是"跑个脚本"——实际投入:
- 数据准备:脱敏、构造查询 → 2-3 天
- 多库部署:每个库独立环境配置 → 3-5 天
- 压测脚本:5 个维度 × 3 个库 = 15 种组合 → 3-5 天
- 分析和报告 → 1-2 天
总计 2-3 周。小项目可以简化(只测 2 个库 + 简化维度)、大项目不能省——选错向量库的切换成本是压测投入的 10-50 倍。
压测之后还要做什么
压测结果 ≠ 生产表现。压测完还要:
- 灰度上线:选中的库先给 10% 真实流量、观察一周
- 长期稳定性测试:跑 7 天 sustained workload、看有没有慢泄漏
- 故障演练:主动 kill 节点、看恢复行为
- 运维工具链测试:备份、恢复、升级、扩容各跑一次
只压性能不压生产可用性、上线仍可能翻车。
11.14 向量库和业务 DB 的一致性:outbox 与 CDC
§11.4 提过 pgvector 的 ACID 优势、§11.10 提过 JOIN 业务 DB 的取舍——但没展开一个更深的问题:当 chunk 的业务数据在关系型 DB(PostgreSQL / MySQL)里、向量在向量库里时、如何保证两者同步?这是 RAG 里跨系统一致性的核心难题。处理不好、向量库里有 chunk 但业务 DB 没有了(或反之)——幽灵数据事故。两种主流解法是 outbox 和 CDC。
两阶段写的问题
两次写之间没有事务保护——任一次失败、两个系统就不一致。这是分布式事务的经典 2PC 问题、但 RAG 场景不能用 2PC(向量库不支持)——需要最终一致性方案。
Outbox 模式
Outbox(事务性发件箱):业务数据和变更事件在同一 DB 事务里写、异步 worker 把事件同步到向量库。
sql
-- 业务写入 + outbox 事件 在同一事务
BEGIN;
INSERT INTO documents (id, text, metadata) VALUES (...);
INSERT INTO outbox (event_type, doc_id, payload, created_at)
VALUES ('doc_created', 'doc-123', '{...}', NOW());
COMMIT;
-- 独立 worker 消费 outbox、同步到向量库
SELECT * FROM outbox WHERE processed = false ORDER BY created_at LIMIT 100;
-- 处理每条、成功后标 processed = true关键属性:
- 原子性:业务写和事件写在同一 DB 事务——要么都成功、要么都失败
- 幂等消费:worker 同步到向量库时用 doc_id 作 key、重复消费等价于一次
- 最终一致性:业务 DB 领先、向量库滞后几秒到几分钟——可接受
Outbox 的实现细节
实际实现需要注意:
sql
CREATE TABLE outbox (
id BIGSERIAL PRIMARY KEY,
event_type VARCHAR(50) NOT NULL,
doc_id TEXT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
processed_at TIMESTAMPTZ,
error_msg TEXT,
retry_count INT DEFAULT 0
);
CREATE INDEX idx_outbox_unprocessed ON outbox (created_at)
WHERE processed_at IS NULL;Worker 逻辑:
- 轮询 or 订阅:PG 可用 LISTEN/NOTIFY 避免轮询
- Worker 锁:多 worker 并行消费时、每条事件只被一个 worker 处理(用
SELECT ... FOR UPDATE SKIP LOCKED) - 失败重试:worker 处理失败 → retry_count +1、到阈值进 DLQ
- 水位监控:outbox 里未处理事件最老的 created_at——代表"当前落后多久"
CDC(Change Data Capture)模式
CDC 是另一条路——直接监听 DB 的 binlog/WAL、把变更流转换成事件:
- MySQL:Debezium / Maxwell 解析 binlog
- PostgreSQL:logical replication / wal2json / Debezium
- MongoDB:change streams
应用完全不需要写 outbox 表——数据库变更自动流出:
Outbox vs CDC 的对比
| 维度 | Outbox | CDC |
|---|---|---|
| 侵入应用代码 | 需要(写 outbox 表) | 不需要 |
| 基础设施 | 轻(一张表 + worker) | 重(Kafka + Debezium) |
| 延迟 | 轮询间隔级 | 近实时 |
| 可靠性 | 高(事务保证) | 高(binlog 不丢) |
| 运维 | 简单 | 复杂(CDC 集群运维) |
| 大规模适配 | 中 | 强 |
选择:
- 小中规模(DB 单机、QPS < 1k):Outbox 简单有效
- 大规模 / 已有 Kafka 栈:CDC 更自然
- 多下游消费者(不只 RAG、还有分析 / 搜索):CDC(事件流给所有下游)
最终一致性的 trade-off
两种方案都是最终一致性——意味着业务 DB 和向量库之间有短暂窗口不一致:
- 用户刚创建一份文档、向量库里可能几秒内还没有
- 用户刚删一份文档、向量库里可能几秒内还能检索到
这在多数 RAG 场景可接受——用户预期 "刚上传、稍等一下"。但某些场景不行(如删除敏感数据必须立即生效)——要用 synchronous fallback(删除时同步调向量库 API、不走 outbox)。
冲突和顺序处理
CDC / Outbox 都是事件流——事件顺序关键:
- Doc 被创建 → update → delete、事件必须按此顺序到向量库
- 并发 worker 消费时乱序 → delete 可能在 create 之前被处理 → 报错
解决:
- 按 doc_id 哈希分 partition:同 doc_id 的事件都进同一 partition、顺序消费
- 版本号:每次更新带 version++、worker 检查 version 递增、过期事件跳过
- 幂等写入:向量库 upsert、即使事件乱序最终状态也正确
监控一致性
生产必监控:
outbox_lag:outbox 里最老未处理事件的时延、健康 < 30svector_db_drift:随机抽样业务 DB 和向量库对比、不一致率 < 0.01%event_processing_rate:事件处理 QPS、对比写入 QPS 看是否跟得上dlq_size:DLQ 里的事件数、应低
反模式
- 应用层直接双写:没有 outbox / CDC、第一次失败就不一致
- outbox worker 单实例:worker 挂了 outbox 一直积压
- 事件顺序错乱:delete 在 create 前、向量库报 not found
- 不做 consistency check:不一致悄悄累积、半年才发现
- 删除走最终一致性:敏感数据删除延迟几秒、合规风险
简化场景:pgvector 的特殊性
如果向量和业务都在同一个 PostgreSQL(pgvector 模式)——两者同事务 ACID、上面的问题不存在:
sql
BEGIN;
INSERT INTO documents (id, text) VALUES (...);
INSERT INTO chunks (doc_id, text, embedding) VALUES (...);
COMMIT;这是 §11.4 讨论的 pgvector 优势——一致性免费。代价是性能和规模不如专用向量库。中小项目这个权衡经常值。
什么时候可以不用 outbox / CDC
最简单的场景、应用只有一个 ingest 入口、并发度低、能接受"写失败就手动重试"——可以不用这些复杂方案。但规模一上来(多个 ingest 源、并发高、对一致性要求严)、这些方案就不是可选的而是必须的。
11.15 向量库 payload schema 的设计最佳实践
前面几节讨论了向量库的选型 / 部署 / 一致性——但chunk 的 payload 到底应该有哪些字段、怎么组织?这个 schema 设计是上线前最后一个关键决策、但经常被低估——字段设错了、后面加字段容易、但改字段意义 / 改 filter 逻辑都要重建索引。这节给出 payload schema 的设计原则。
为什么 schema 很重要
这些问题都是 schema 阶段没想清楚的后果——一次性设计 vs 长期改成本差几个数量级。
payload 字段的分类
好的 payload 按用途分类:
json
{
// 1. 身份字段(永不变)
"chunk_id": "doc-123-chunk-7",
"doc_id": "doc-123",
"source_id": "confluence-page-456",
"content_hash": "sha256:abc...",
// 2. 检索过滤字段(频繁 filter)
"tenant_id": "acme",
"language": "zh",
"doc_type": "pricing",
"visibility": "internal",
"access_level": 2,
"published_at": "2026-04-01",
// 3. 排序加权字段(rerank 时用)
"authority_score": 0.8,
"freshness_bucket": "last_30d",
"doc_title": "2026 定价",
// 4. 展示字段(UI 用)
"text": "...", // chunk 文本
"section_path": ["产品", "定价"],
"source_url": "https://...",
// 5. 血缘字段(调试追溯)
"indexed_at": "2026-04-25T10:00",
"parser_version": "v3.2",
"chunk_strategy": "structured-v2",
"embedding_model": "bge-m3-v2"
}五类字段各有不同的访问模式——按访问模式分类设计。
字段类型的选择
| 字段类型 | 向量库支持 | 用途 |
|---|---|---|
| string | 所有 | 身份、分类 |
| int / float | 所有 | 分数、数值 filter |
| bool | 所有 | 二元 filter |
| datetime / timestamp | 大多数 | 时间 range filter |
| array of string | 多数 | 标签、多值 filter |
| nested object | 部分(Qdrant / Milvus) | 复杂结构 |
| JSONB | pgvector | 灵活字段 |
类型选择的常见错误:
- 日期存成 string:"2026-04-25" → 能 exact match 但不能 range filter
- 布尔存成 int(0/1):可以但不如 bool 语义清楚
- 多值存成逗号分隔 string("tag1,tag2,tag3"):filter 时要字符串匹配、不高效
索引字段 vs 不索引字段
向量库对 payload 字段可以建 secondary index:
- 索引字段:filter 查询快、但占存储、写入慢
- 不索引字段:只做展示 / 元数据、不能 filter、存储少
决策规则:
- 每 query 都过的 filter(如
tenant_id)→ 必索引 - 偶尔 filter(如
doc_type)→ 索引 - 只展示不 filter(如
source_url)→ 不索引 - 纯调试字段(如
parser_version)→ 不索引、甚至不存 chunk 里(存独立 log)
Qdrant / Milvus / pgvector 都支持字段级索引——定义清楚每个字段是否要 index。
Schema 演进:加字段和删字段
加字段:一般容易
python
# 新 chunk 带新字段
new_chunks = [{"text": "...", "new_field": "..."} for c in chunks]
# 老 chunk 的新字段是 null但 filter 时要处理 null:new_field = "X" OR new_field IS NULL——注意语义。
删字段:几乎不做
向量库里 payload 的字段通常只能停止写入新值、不能真删除(历史数据还带着)。如果真要删:
- 方案 1:backfill 把所有 chunk 的该字段置 null(§8.13)
- 方案 2:全量重建(§8.6)——正规途径
改字段语义:最危险
从 "int" 改成 "string"——所有历史数据要重 embed。等同于全量重建。schema 设计时想清楚、尽量不改。
敏感字段的处理
某些字段涉及敏感信息——schema 要特殊处理:
- PII(身份证、手机号):脱敏后存、或用 hash
- 密级:明确用 int 或 enum、不用 string(防止 typo)
- 权限 ACL:规范化(如
["role:eng", "role:prod"])、便于 filter 下推
不敏感但涉及合规的:retention_policy、legal_hold 等——设计初期就留位置。
Schema 的命名规范
小事但重要——字段命名不统一会长期折磨:
- snake_case(推荐):
chunk_id、doc_type - 不用 camelCase:和 JSON 标准不一致
- 语义清楚:
created_at比ts好、access_level比al好 - 前缀区分:
meta_*前缀分 metadata 字段、score_*前缀分分数字段
一套命名规范写进文档、所有新字段都遵守——避免"半年后字段名五花八门"的情况。
版本化 schema
Schema 会演进——记录版本:
json
{
"schema_version": "v3",
"chunk_id": "...",
// ...
}Schema 变更时:
- v1 → v2:加字段、schema_version 升级
- Migration:把老版本的 chunk 按新 schema 补齐
- 查询层:可 filter "只要 v2+" 或兼容两版
版本化让平滑演进可能——没版本号就只能"一刀切"。
Payload 大小的权衡
每个 chunk 的 payload 越大、存储和网络成本越高:
| Payload 大小 | 100 万 chunk 存储 | 单次查询 response 大小 |
|---|---|---|
| 小(~200 bytes) | 200 MB | 2 KB |
| 中(~500 bytes) | 500 MB | 5 KB |
| 大(~2 KB) | 2 GB | 20 KB |
别把 chunk 的 text 也存 payload 里——text 大、存到元数据 DB 里、payload 只存 text_id。向量库的 payload 只存 filter 相关字段。
常见 schema 设计错误
- 扁平化过度:所有字段在 root、没有结构——复杂 filter 写起来难看
- 过度嵌套:深度 5+ 的 nested object——向量库 filter 性能降
- 字段类型不一致:同一字段在不同 chunk 里类型不一样(int / string 混)——filter 出错
- 用文本值代替 enum:
doc_type: "pricing"vsdoc_type: "price"vsdoc_type: "定价"——混乱 - 没 default value:老 chunk 缺字段、filter 结果不稳
- 字段语义不文档化:三个月后团队成员不知道
score_3代表什么
设计时的检查清单
上线前必对:
- [ ] 每个字段都有文档说明(用途、类型、可能值)
- [ ] 必索引的字段明确(基于预期 filter 模式)
- [ ] 敏感字段有脱敏 / 加密
- [ ] Schema 版本号机制
- [ ] 命名规范统一
- [ ] 字段类型经过 filter 测试
- [ ] Payload 总大小预估合理(< 1KB)
从小做起、渐进加字段
MVP 阶段 schema 简单:chunk_id / text / tenant_id / doc_id 够用。
后续按业务加字段——每次都更新 schema 文档、明确版本。
这种渐进方式比"一次设计周全"实际——因为业务永远会变、预测不准。保留扩展空间更重要。
Schema 的 review 节奏
- 每季度 review 一次 schema——
- 哪些字段频繁 filter、应该索引?
- 哪些字段从不使用、可以删?
- 新业务需求要加哪些字段?
没 review、schema 会变得"历史包袱"——字段多但没人用、新需求又不敢加。
这不只是技术决策
Schema 设计跨角色:
- 产品:定什么业务字段重要
- 工程:定技术类型和索引
- 合规:定敏感字段处理
- 数据:定命名规范和 governance
单一角色决定 schema 往往偏——跨角色 review 一次、比单打独斗强。
11.16 向量库的备份、恢复与灾难恢复
§11.7 briefly 提到备份——但具体怎么备份、怎么恢复、遇到灾难怎么处理?这是生产向量库最容易被忽视、出事时最后悔的环节。向量库的备份和传统 DB 不同——索引大、重建慢——设计好备份策略是上线前的必修课。这节给向量库 BR / DR 的实用指南、和 ch22 §22.14 的灾难恢复呼应但专门针对向量库。
备份的必要性
前四项 99% 的团队都会遇到——不是"会不会"、是"什么时候"。没备份 = 等死。
三层备份策略
不同层级的备份应对不同场景:
L1:元数据和 chunk 正文
- 存业务 DB(PG / MongoDB)
- DB 自身的每日 backup
- 最容易恢复、最重要
L2:向量索引快照
- 向量库的 snapshot 功能(Qdrant / Milvus / pgvector 都有)
- 频率:每日 / 每周
- 恢复:加载 snapshot、几分钟到几小时
L3:原始文档
- 存对象存储(S3 / OSS)
- 最终兜底、从头重建索引
- 恢复:几小时到几天(取决于规模)
三层组合:
- L1 坏 → DB 恢复 + L2 索引快照加载
- L2 坏 → 从 L1 重建(重新 embed)
- L3 坏 + L1/L2 都坏 → 业务重大事故、但至少有 L3
备份的频率
不同层频率不同:
| 层 | 频率 | RPO | 成本 |
|---|---|---|---|
| L1 业务 DB | 每小时 / 持续 WAL | 15min | 低 |
| L2 向量索引 | 每日 | 24h | 中 |
| L3 原始文档 | 每次 upload | 0 | 低 |
重要的是 L1 和 L3——这两个都在、L2 总能重建(慢但可行)。
备份的实施
Qdrant 的备份:
python
# Qdrant 自带 snapshot API
qdrant_client.create_snapshot(collection_name="my_chunks")
# 产出 .snapshot 文件、可下载
# 恢复
qdrant_client.upload_snapshot(
collection_name="my_chunks_restored",
location="path/to/snapshot"
)Milvus 的备份:
backup-toolCLI 导出- 支持 incremental backup
- 恢复用 restore 命令
pgvector:
- Postgres 本身的 pg_dump / WAL
pg_dump -Fc dbname > backup.dump- 恢复用 pg_restore
每个向量库自己的 backup 工具——用原生工具、不要造轮子。
备份存哪里
备份文件存储:
- 对象存储(S3 / OSS):便宜、持久
- 跨区域复制:防单区域故障
- Immutable storage:防止被攻击删除(WORM)
- 加密:备份文件也加密(§9.15)
不要存在向量库同区域 / 同磁盘——本地故障时备份也跟着没。
恢复的演练
备份跑 vs 能恢复是两件事:
python
# 每月自动演练
def monthly_restore_drill():
# 找一个非生产环境
sandbox = spin_up_sandbox()
# 从备份恢复
latest_backup = find_latest_backup()
try:
sandbox.restore(latest_backup)
# 跑基本测试
run_smoke_tests(sandbox)
log_drill_success()
except Exception as e:
log_drill_failure(e)
alert_team("Backup restore drill FAILED")每月一次、发现问题及时修——只备份不演练 = 自欺欺人。
RPO 和 RTO 设计
RPO (Recovery Point Objective):最多能丢多少数据?
- 高要求:RPO < 1 小时(近实时备份)
- 一般:RPO 24 小时(每日备份)
RTO (Recovery Time Objective):恢复要多久?
- 高要求:RTO < 1 小时
- 一般:RTO < 12 小时
业务方定这两个目标——工程按目标设计备份策略。不是工程单方决定。
跨区域灾难恢复
单区域备份不够——整个区域故障呢?
- 跨区域复制:主区域 + 备区域、数据双写或近实时同步
- 跨区域恢复:主区域挂、备区域接管(可能需要人工介入)
- 演练:季度做一次跨区域 failover 演练
跨区域 DR 的成本——存储 + 网络 × 2——但对合规和可用性有要求的场景必须。
灾难恢复的分级
不是所有事故都要"全量恢复":
| 级别 | 场景 | 响应 |
|---|---|---|
| L1 | 单条 chunk 错 | 重 embed 单条 |
| L2 | 小部分数据损坏 | Backfill 局部(§8.13) |
| L3 | 整个 collection 损坏 | L2 快照恢复 |
| L4 | 向量库整体崩 | 新实例 + 全量恢复 |
| L5 | 区域故障 | Failover 跨区域 |
等级和响应匹配——不要小事故用大响应、也不要大事故用小响应。
备份的成本
备份不是免费:
- 存储:索引副本 × 备份个数——可能 3-5× 主存储
- 带宽:跨区域同步、大数据量贵
- 计算:压缩 / 加密 / 验证
典型:备份成本是主存储的 10-30%——视策略。
合规驱动的备份要求
某些法规明确要求:
- SOC 2:每日备份、每年恢复演练、文档齐全
- HIPAA(医疗):备份必加密、保存期限长
- 金融行业:双区域、保留 5-7 年
- GDPR:备份也遵守 "right to erasure"
合规方给明确要求——工程实施、定期审计。
备份的监控
生产必监控:
backup_success_rate:成功率应 100%backup_duration:时长、异常变长说明数据量异常或系统慢backup_size:大小、异常暴涨说明数据质量问题last_successful_backup_age:离现在多久、应 < 备份间隔restore_drill_result:最近一次恢复演练的成功
没监控的备份——"最后发现备份空文件"的事故常见。
备份的常见反模式
- 没备份:自信不会出事
- 只备份数据、不备份 schema / 配置:恢复时数据有、但索引配置丢
- 备份和生产同位置:一起遭殃
- 不演练:真需要时发现恢复不能
- 备份不加密:数据被偷
- 保留期太短:7 天前的数据挽回不了
- 备份文件命名混乱:不知道哪个是最新 / 完整的
灾难恢复 playbook
出事时用的 playbook 样例:
markdown
# Playbook: Vector DB collection corrupted
## 症状
- Query 报 "collection unavailable" 或结果异常
## 立即动作(5 分钟内)
1. 切到 fallback(BM25 only)
2. 通知 on-call 和业务方
## 恢复(1-4 小时)
1. 查最近 backup:`./scripts/list_backups.sh`
2. 创建新 collection:`./scripts/create_from_backup.sh <backup-id>`
3. 验证数据完整性:`./scripts/verify_collection.sh`
4. 切回正常路径
## 事后
- 写 postmortem
- 分析为什么 collection 坏了
- 修 root cause每个常见场景都有 playbook——on-call 不用现场 "想办法"。
和 ch22 §22.14 灾难恢复的关系
ch22 §22.14 讲全系统灾难恢复——本节向量库专门:
- ch22 是跨组件全局视角:所有组件的 RPO/RTO、演练节奏
- 本节是向量库深度:具体备份工具、snapshot 操作、collection 恢复
两者结合看完整——不要重复但要配合。
不要忽视的细节
- 快照和增量的一致性:确保快照期间没有写入、或用数据库的 consistent snapshot
- 大 collection 的 backup 分片:超大 collection 分片备份、并行恢复
- 测试期的 backup 分离:staging 的 backup 和 prod 分开、防止污染
对新项目的建议
从 day 1 就设计 backup:
- Week 1:配置基本 backup(每日)
- Month 1:第一次恢复演练
- Month 3:跨区域 backup(如果合规需要)
- Month 6:完整 playbook + 演练记录
别等"系统大了再做"——数据丢过一次、团队才重视——太晚。
备份能力的 ROI
备份能力的投入:
- 初始配置:1-2 人周
- 演练:每月 2-4 小时
- 监控和维护:每季度 1 人天
- 事故时恢复:几小时 - 1 天
收益:
- 避免数据永久丢失
- 减少事故 MTTR
- 满足合规要求
- 团队睡得踏实
一次数据丢失事件的代价 = 备份建设的 10-100×——没做过"数据丢光了"的团队很难想象。
11.17 跨书关联:专用 DB vs 通用 DB 的哲学
向量数据库的出现重演了 OLAP 数据库的历史。2010 年代之前,大家用 MySQL 做所有事——后来 ClickHouse / Druid / BigQuery 这些专用 OLAP 引擎崛起,因为 OLAP 场景和 OLTP 场景的硬件/算法需求完全不同。向量检索同样——通用 DB 做 HNSW 不如专用库。但 pgvector 说明另一件事:如果专用需求的规模和复杂度不高,用好通用工具比引入专用组件更划算。
这种"通用 vs 专用"的张力在所有系统设计里反复出现。《Tokio 源码深度解析》讨论过专用 async runtime 和通用 thread pool 的取舍,《vLLM 推理内核深度解析》讨论过专用 KV Cache 和通用 GPU 内存分配器的取舍。都是同一场辩论。
11.18 本章小结
- 向量数据库 = ANN 算法 + 存储、HA、分布式、过滤、运维 一体化封装
- 三大主流:Qdrant(Rust 原生开发友好)、Milvus(云原生亿级)、pgvector(复用 Postgres)
- 选型五问题:规模 / 元数据 / 现有栈 / 部署 / 运维——决策矩阵清晰
- 其他方案各有位置:Weaviate / Elasticsearch / Redis / Pinecone / LanceDB
- 生产运维共性:容量规划 / 备份恢复 / 监控 / 版本升级——不论选哪个都要做
- 自托管 vs 托管是成本和复杂度的权衡——小规模推托管 / pgvector、大规模自托管
下一章讨论稀疏检索——为什么 2020 年代的 BM25 仍然是生产 RAG 不可或缺的召回源。