写在前面 做 RAG 的人大多会遇到同一个问题:向量、正文、元数据到底放在一起还是分开?这篇讲我们的选择和理由,也讲这个选择带来的麻烦。
一、最省事的做法
最直接的方案是:一张表,同时存正文、元数据和向量。查询时一次搞定,代码简单。
小规模下这样完全没问题。我们最初也是这么做的。
问题出现在数据量上来之后:
- 向量列很占空间。 一个高维向量(如 1536 维)本身就不小,和正文放在一起会让表迅速膨胀。一条记录的正文通常是几千到几万字符,而一个向量就是 6KB。当切片很细时,向量的存储体积可能超过正文。
- 检索和读取的访问模式完全不同。 向量检索是计算密集的、扫描全表的;读正文是点查、走索引的。放在一起,两种负载互相干扰。
- 索引重建代价高。 向量索引需要定期重建,而它和业务数据在同一张表上,重建期间会影响正常读写。
- 备份策略冲突。 向量可以从正文重新生成,正文丢了就真丢了。它们的备份优先级不一样,却被绑在一起。
一个具体的数据对比
假设 10 万条文档切片,每条正文平均 2000 字符,向量 1536 维:
- 放一起:单表约 1.5GB(正文) + 0.6GB(向量) = 2.1GB,加上向量索引额外 1-2GB
- 分开:主库 1.5GB(只需关注业务读写),向量库 0.6GB(专门为检索优化)
2GB 的表在单机上是完全可控的。但 100 万条切片时,情况就不同了——向量存储 6GB,向量索引 10-20GB,重建索引可能需要几分钟。放在一张表上,这几分钟里所有依赖这张表的查询都会受影响。
二、我们的拆法
现在的结构是:
主库(PostgreSQL)——存资源元数据、分段正文、用户、权限、订单等全部业务数据。这是权威数据源。
向量库(独立的 pgvector 实例)——只存三样东西:chunk_id、拥有者 user_id、向量本身。
检索流程变成两步:
① 在向量库里按相似度检索 → 拿到一组 chunk_id
② 用 chunk_id 回主库读正文和元数据 → 组装上下文多了一次查询,但换来几件事:
- 向量库可以按检索负载独立扩容,不影响主库
- 向量索引重建不会波及业务读写
- 主库的备份和迁移策略不受向量数据拖累
- 向量数据可以随时从正文重新生成,丢了不致命
user_id 冗余在向量库里,是为了在检索阶段就能做权限过滤——不能先检索出全部结果、回主库读完再过滤。那样越权数据已经进入过候选集,风险和成本都不划算。
性能收益
拆分之后,主库的读写性能得到了明显改善:
- 向量索引重建不再影响业务查询——重建期间主库的写操作无感知
- 主库的备份体积缩小了约 40%,备份和恢复时间同步缩短
- 检索压力独立扩容——如果某天搜索量激增,只需扩向量库实例,不影响其他业务
三、代价
这个拆分不是白拿的:
- 多一跳网络往返。 在延迟敏感的场景需要通过批量读取来摊平。我们的做法是一次性拿到 chunk_id 列表后,用
IN查询批量读回正文,把 N 次查询合并为 1 次。 - 两套数据要保持一致。 正文删了,向量得跟着删;正文改了,向量得重新生成。我们靠任务队列来做这件事,但仍然需要定期的一致性校对。
- 运维对象变多。 两个数据库实例,两套监控。
一致性校对的实现
我们写了一个定时任务,每天晚上低峰期运行:
- 从主库导出所有 chunk_id(约 20 万条,分页查询)
- 从向量库导出所有 chunk_id
- 做差集比较:
- 向量库有、主库没有的 → 标记为"孤儿向量",加入清理队列
- 主库有、向量库没有的 → 标记为"缺失向量",加入重建队列
- 两个队列分别处理
这个校对任务跑一次大概 3-5 分钟,对线上无影响。
四、为什么还要一个图数据库
除了主库和向量库,我们还用了图数据库存记忆图谱的拓扑。三套存储,职责是这样分的:
| 存储 | 回答的问题 |
|---|---|
| 关系库 | "这份资料的元数据是什么、正文在哪、谁有权限" |
| 向量库 | "和这个问题语义最接近的片段是哪些" |
| 图数据库 | "这个概念和哪些概念相关、隔几跳能连到哪里" |
第三类查询用关系型数据库做多表自连接也能实现,但写起来痛苦、性能也差。多跳关系查询和可视化是图数据库的主场。
一个图查询的例子
"找出和『强化学习』相关的所有主题,以及这些主题相关的洞察,以及这些洞察引出的行动项"——这是三跳查询。
在 SQL 里相当于三次 JOIN,每次 JOIN 都是一次自连接。在 Cypher 里是一行:
MATCH (t:Topic {name: '强化学习'})
-[:RELATES_TO*1..2]->(related:Topic)
-[:RELATES_TO]->(insight:Insight)
-[:LEADS_TO]->(action:Action)
RETURN related.name, insight.content, action.content不是 SQL 做不了,是图数据库在这个场景下语义更自然、查询更快。
需要说明的是:图谱的权威数据仍然存在关系库里,图数据库存的是同步过去的拓扑副本。这样做的原因是权限、事务和溯源这些能力在关系库里更成熟,而图数据库只需要服务查询和展示。
五、一个判断标准
如果你在纠结要不要拆,可以问自己三个问题:
- 向量数据和业务数据的增长速度是否差异很大? 差异大就该拆。
- 是否需要独立扩容检索能力? 需要就该拆。
- 团队能否承担多一套存储的运维? 不能就先别拆。
架构没有标准答案,只有和当前规模匹配的选择。我们拆是因为已经被前面几个问题实际困扰过——不是因为拆开更"专业"。
想在你的业务里试试同样的做法?
告诉我们业务问题与数据现状,我们会给出方案建议与落地路径。
需要帮助?
联系我们的团队,获取贴合你业务场景的方案建议。