主题
AI 数据架构选型:先决定向量要不要离开业务库
一句话:选型不是选「最强的向量库」,而是决定向量和业务数据是否必须同源;同源就用 PostgreSQL,规模或隔离要求超出单机再拆。
适用 PostgreSQL 16/17、pgvector 0.8.x。规模分界是经验值,必须用真实数据和查询压测,不是保证值。
为什么需要
RAG 的最小链路是:文档切片 → 向量化 → 存储 → 按问题召回 → 带权限/租户过滤 → 拼 prompt。真正难的不是「存向量」,而是让向量、切片内容、权限字段、业务状态保持一致。
用独立向量库时,切片和向量分在两个系统:写入要双写,删除要双删,过滤条件要么冗余进向量库,要么先召回再回业务库。双写不一致是这类架构最常见的事故源。把向量放进 PostgreSQL,写入、删除、过滤、召回可以在一个事务、一条 SQL 里完成;代价是单机扩展性和索引内存。
核心机制
数据库在 AI 链路里的角色
| 角色 | 是否适合 PostgreSQL |
|---|---|
| 切片内容、文档元数据、权限、租户 | 适合,关系模型 + JSONB |
| 向量存储与 ANN 召回 | 适合,pgvector(千万级以内) |
| 关键词召回 | 适合,tsvector + GIN |
| 会话状态、短期缓存 | 不适合,Redis 更合适 |
| 原始文件、大对象 | 不适合,对象存储更合适 |
| 事件总线、异步任务 | 不适合,消息队列更合适 |
「一个数据库解决所有问题」不是目标;目标是减少需要跨系统保持一致的状态。
统一库与专用库的取舍
- 统一库:少一套同步、少一套高可用、少一套监控;SQL 可以同时做标量过滤、向量排序、全文检索,甚至 JOIN 业务表。
- 专用向量库:在亿级向量、高 QPS、多租户隔离、弹性扩缩、专用索引上有优势;代价是双写、回表、跨系统一致性、额外运维。
判断顺序:
- 向量规模是否超出单机内存和单机 QPS?
- 过滤条件是否强依赖业务表,且要求事务一致?
- 是否需要跨租户隔离、独立扩缩、专用索引能力?
- 团队是否愿意维护两套系统?
前两个问题偏向 PostgreSQL,后两个偏向专用向量库。
混合检索:不要直接加分数
向量距离和 ts_rank_cd 的量纲、方向都不同,直接相加没有意义。常见做法是分别取 topN,再用倒数排名融合(Reciprocal Rank Fusion,RRF)合并:
score(d) = Σ 1 / (k + rank_i(d))k 常取 60。它只依赖排名,不依赖分数尺度,适合向量召回和关键词召回的融合。
过滤与召回的冲突
先 ANN 取 topK 再过滤,过滤条件一严,topK 可能被筛空。pgvector 0.8 的迭代扫描可以继续扫,但延迟会上升;另一种做法是给高选择性过滤列建 B-tree,让优化器先过滤再算距离。两种方案都要用 EXPLAIN 和 recall@K 验证。
最小上手
sql
-- 前提:已按 05 篇建立 chinese 全文配置;演示用 3 维向量,生产按模型改
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE docs (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL,
status text NOT NULL,
content text NOT NULL,
tsv tsvector GENERATED ALWAYS AS (to_tsvector('chinese', content)) STORED,
embedding vector(3) NOT NULL
);
CREATE INDEX idx_docs_tsv ON docs USING GIN (tsv);
CREATE INDEX idx_docs_embedding ON docs USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
INSERT INTO docs (tenant_id, status, content, embedding) VALUES
(1, 'published', '分布式事务需要协调多个参与者', '[0.1,0.2,0.3]'),
(1, 'published', '全文检索把文本拆成词位', '[0.9,0.1,0.1]'),
(2, 'draft', '发票开具流程', '[0.2,0.8,0.2]');
SET hnsw.ef_search = 100;
-- 带租户/状态过滤的向量召回
SELECT id, embedding <=> '[0.1,0.2,0.3]'::vector AS distance
FROM docs
WHERE tenant_id = 1 AND status = 'published'
ORDER BY embedding <=> '[0.1,0.2,0.3]'::vector
LIMIT 5;
-- 向量 + 关键词混合检索:RRF 融合,不直接加分数
WITH vec AS (
SELECT id, row_number() OVER (ORDER BY embedding <=> '[0.1,0.2,0.3]'::vector) AS rnk
FROM docs
WHERE tenant_id = 1 AND status = 'published'
ORDER BY embedding <=> '[0.1,0.2,0.3]'::vector
LIMIT 50
),
kw AS (
SELECT id, row_number() OVER (ORDER BY ts_rank_cd(tsv, q) DESC) AS rnk
FROM docs, websearch_to_tsquery('chinese', '分布式 事务') q
WHERE tenant_id = 1 AND status = 'published' AND tsv @@ q
LIMIT 50
)
SELECT id, sum(1.0 / (60 + rnk)) AS rrf_score
FROM (
SELECT id, rnk FROM vec
UNION ALL
SELECT id, rnk FROM kw
) s
GROUP BY id
ORDER BY rrf_score DESC
LIMIT 10;如果 websearch_to_tsquery('chinese', ...) 报配置不存在,说明中文分词还没装好;先回到 05 篇处理,不要用 simple 假装中文检索可用。
生产动作
- 容量估算:float32 每维 4 字节,1536 维约 6KB/条;100 万条约 6GB 原始向量,再加 HNSW 图、索引页、行头和缓存。先算热数据,不要把冷数据全塞内存。
- 写入幂等:用
(doc_id, chunk_no, model_version)唯一约束或内容哈希,避免重复切片和重复计费;向量写入和业务状态更新放在同一事务。 - 模型版本:表里保留
embedding_model、model_version;换模型时加新列/新表,回填后双读对比,再切流量,不要原地覆盖。 - 索引构建:HNSW 构建慢且吃
maintenance_work_mem;在低峰期用CREATE INDEX CONCURRENTLY,先估算时间和内存。 - 召回质量:用精确扫描做 ground truth,定期测 recall@K;召回下降先看过滤条件、
ef_search、模型版本和索引是否失效。 - 监控:向量查询 p99、索引大小、
n_dead_tup、缓存命中、每秒写入向量数、复制延迟。pg_stat_statements里按total_exec_time看向量查询的占比。 - 租户隔离:每条查询都带
tenant_id;需要更强隔离时用行级安全(RLS)或按租户分区。不要只靠应用层拼 SQL。 - 备份:向量列就是普通列,随表备份;但恢复后要重建/校验 ANN 索引,并确认模型版本与向量维度一致。
规模分界线
| 规模/约束 | 更合适的选择 | 说明 |
|---|---|---|
| 百万级以内,已有 PostgreSQL,需要事务/JOIN | pgvector | 运维最省事,一条 SQL 完成过滤 + 召回 |
| 百万到千万,单机内存足够,过滤复杂 | pgvector + 分区/只读副本 | 必须压测 p99 和 recall@K,确认内存余量 |
| 千万以上到亿级,高 QPS,多租户隔离 | 专用向量库(Milvus 等) | 分布式、弹性扩缩、专用索引更合适 |
| 需要跨地域、GPU 索引、复杂多路召回 | 专用向量库 + 业务库 | 接受双写和回表,做好一致性补偿 |
这些是社区常见经验分界,不是硬指标;同样 1000 万条,1536 维和 256 维的内存差好几倍。
边界
- PostgreSQL 不是分布式向量库:单机写入、索引构建、内存都是上限;分片要靠 Citus 或按租户分区,跨分片 topK 归并要自己做。
- 向量索引常驻内存:HNSW 内存不够时延迟断崖,不会优雅降级;容量规划要留余量。
- 过滤会破坏召回:后过滤在条件严格时最危险;迭代扫描能缓解,但延迟上升。
- 统一库不是「一个库存所有东西」:原始文件放对象存储,会话状态放 Redis,事件放消息队列;PostgreSQL 负责需要事务一致的那部分。
- 扩展有版本和运维成本:pgvector、zhparser、pg_partman 都要跟着 PostgreSQL 大版本升级;升级前在测试环境验证索引兼容性。
- 不要为了「万一的亿级」提前拆:双写、回表、跨系统一致性是持续性成本;先用真实规模验证分界线。
讲清楚
AI 数据架构选型,核心不是「哪个向量库性能高」,而是「哪些状态必须放在一起」。切片内容、向量、租户、权限、业务状态如果强相关,放在 PostgreSQL 里可以用一个事务、一条 SQL 完成写入、过滤和召回,省掉双写和回表;代价是单机内存和扩展性。规模到千万级以上、QPS 很高、需要多租户隔离或弹性扩缩,再把向量拆到专用库,接受双写和一致性补偿。混合检索不要直接把向量距离和文本分数相加,量纲不同;用 RRF 按排名融合。最后用 recall@K、p99、内存和写放大做决策,而不是用「AI 原生」这类标签。