文章摘要
企业RAG平台从单客户扩展到数百、数千甚至数万个租户后,向量数据库必须回答一个核心问题:不同租户的数据应该放在同一个Collection中,通过Payload过滤隔离;还是每个租户建立独立Collection;或者使用自定义分片键、分层多租户和“大租户独立、小租户共享”的混合方案?
这个决定会影响内存、索引数量、过滤性能、热点隔离、备份恢复、合规边界、租户删除和未来迁移。如果过早采用每租户一个Collection,可能产生海量小索引和运维负担;如果所有数据永远放在一个共享Collection,又可能出现过滤性能下降、热点租户互相影响和迁移困难。
本文从Qdrant、Milvus、pgvector等系统都适用的通用原则出发,详细分析三类架构的实现方式、成本模型、适用边界和迁移路径,并给出可落地的Tenant Placement目录、在线迁移、双写和数据一致性设计。
一、为什么多租户向量库不能只讨论“隔离”
传统关系数据库的多租户架构通常比较:
共享表 独立Schema 独立数据库向量数据库还要额外考虑:
- HNSW等索引的固定开销;
- 每个Collection的Segment数量;
- Payload过滤选择性;
- ANN搜索是否能利用过滤条件;
- 向量维度与存储;
- Embedding重建成本;
- 热点租户对缓存和CPU的影响;
- Collection创建和优化时间;
- 分片数量与副本;
- 租户迁移时的重建成本。
因此,“隔离越强越好”并不成立。
一个拥有100个大型企业客户的系统,和一个拥有10万个小团队的SaaS平台,最优方案完全不同。
二、三种核心架构
方案一:共享Collection+Payload过滤
所有租户放在同一Collection:
collection: enterprise_knowledge每个Point/Chunk携带:
{"tenant_id":"T001","document_id":"D1001","status":"EFFECTIVE","knowledge_base_id":"KB01"}查询必须带:
tenant_id == 'T001'数据布局
enterprise_knowledge ├── T001 chunks ├── T002 chunks ├── T003 chunks └── ...优势
- Collection数量少;
- 索引和Segment固定开销低;
- 资源利用率高;
- 新租户无需创建基础设施;
- 小租户可以共享索引和缓存;
- 统一备份和监控;
- 适合大量长尾小租户。
风险
- 过滤条件错误会造成跨租户泄露;
- 高基数租户字段需要合理索引;
- 大租户可能占用大量缓存和CPU;
- 删除单租户数据需要精准过滤;
- 单Collection故障影响范围大;
- 单租户独立恢复更困难;
- 全局索引参数难以适配所有租户。
三、共享Collection适合什么场景
推荐条件:
租户数量多 +单租户数据量相对小 +隔离以逻辑权限为主 +查询总是带tenant过滤 +平台有成熟权限测试典型场景:
- SaaS知识助手;
- 中小企业文档问答;
- 大量项目空间;
- 用户个人知识库;
- 单租户几千到几十万Chunk;
- 租户生命周期频繁创建和删除。
四、共享Collection必须补齐哪些能力
1. tenant_id元数据索引
所有高频过滤字段建立Payload或元数据索引。
2. 强制查询守卫
业务不能直接调用VectorStore。
publicinterfaceTenantScopedVectorStore{List<Document>similaritySearch(AccessContextaccess,TenantSearchRequestrequest);}3. 缓存隔离
Key必须包含:
tenant_id permission_hash index_version query4. 数据完整性约束
每个Chunk必须包含:
- tenant_id;
- document_id;
- status;
- version。
5. 越权测试
持续执行跨租户蜜罐查询。
6. 热点治理
按租户统计:
- QPS;
- 向量数量;
- 查询延迟;
- CPU;
- 缓存命中;
- 写入速率。
达到阈值后可迁移到独立分片或Collection。
五、方案二:每租户独立Collection
每个租户拥有自己的Collection:
tenant_T001_knowledge tenant_T002_knowledge tenant_T003_knowledge优势
- 数据边界直观;
- 查询无需tenant过滤;
- 单租户删除简单;
- 单租户备份和恢复更容易;
- 可设置独立索引参数;
- 大客户性能更可控;
- 合规和专属资源表达更清晰;
- 租户迁移可独立执行。
风险
- Collection数量可能爆炸;
- 每个Collection有索引和Segment开销;
- 大量小Collection浪费内存;
- 创建、优化、备份和监控复杂;
- 元数据变更需要批量操作;
- 平台升级要处理海量对象;
- 查询路由依赖准确的Collection目录;
- 跨租户共享知识更难。
六、为什么“每租户一个Collection”容易被高估
开发者常认为:
Collection独立 =绝对安全但安全仍取决于路由。
如果代码:
Stringcollection=request.getTenantId();仍可能查询错误Collection。
每租户Collection只降低了过滤逻辑错误风险,并没有替代:
- 身份认证;
- 租户归属校验;
- Collection路由目录;
- 缓存隔离;
- 引用鉴权;
- 运维权限。
同时,海量小Collection可能比一个共享Collection更难维护。
七、独立Collection适合什么场景
推荐条件:
租户数量较少 +单租户数据量大 +需要独立备份恢复 +需要定制索引 +需要强资源隔离典型场景:
- 少量大型企业客户;
- 专属实例或专属资源套餐;
- 租户数几十到数百;
- 单租户数千万向量;
- 监管要求独立恢复;
- 每个租户Embedding维度或距离算法不同;
- 大客户有明显性能SLA。
八、方案三:自定义分片键或分区
在一个逻辑Collection中,通过分片键将租户路由到特定Shard。
概念:
collection: enterprise_knowledge shared shard: 小租户A/B/C/D dedicated shard T100: 超大租户T100 dedicated shard T200: 热点租户T200查询时同时提供:
tenant payload filter +shard key selector优势
- 保留统一Collection管理;
- 大租户可独立Shard;
- 查询只访问目标Shard;
- 热点隔离;
- 小租户仍共享资源;
- 支持租户晋升;
- 更适合租户规模差异显著的系统。
风险
- 需要维护Tenant→Shard映射;
- 分片数量不能无限增长;
- 迁移过程复杂;
- 双写和一致性要求高;
- 不同数据库能力差异较大;
- 运维与容量规划要求更高。
九、为什么分片键不能直接使用所有tenant_id
如果有10万个租户,就创建10万个物理Shard,通常不可行。
物理Shard有:
- 元数据;
-副本; - Segment;
- 调度;
- Raft或一致性开销;
- 内存;
- 运维对象。
因此,应区分:
逻辑租户分区 与 物理Shard大量小租户更适合Payload分区;少量大型租户才适合专用Shard。
十、方案四:分层混合架构
实际生产中最常见的最优方案:
小租户 → 共享Collection/共享Shard 中型租户 → 指定共享Shard组 大型或高SLA租户 → 独立Shard或独立Collection 监管专属租户 → 独立集群这是一种“租户放置策略”。
租户层级
publicenumTenantVectorTier{SHARED,GROUPED,DEDICATED_SHARD,DEDICATED_COLLECTION,DEDICATED_CLUSTER}放置记录
publicrecordTenantVectorPlacement(StringtenantId,TenantVectorTiertier,StringclusterId,StringcollectionName,StringshardKey,StringembeddingProfile,StringplacementVersion,PlacementStatusstatus){}业务查询不能自己拼Collection名称,必须通过Placement Service。
十一、统一Tenant Placement Service
@ServicepublicclassTenantVectorPlacementService{privatefinalTenantPlacementRepositoryrepository;publicTenantVectorPlacementrequire(StringtenantId){TenantVectorPlacementplacement=repository.findActiveByTenantId(tenantId).orElseThrow(()->newMissingPlacementException(tenantId));if(placement.status()!=PlacementStatus.ACTIVE){thrownewPlacementNotReadyException(tenantId,placement.status());}returnplacement;}}检索器:
publicList<Document>search(AccessContextaccess,Stringquery){TenantVectorPlacementplacement=placementService.require(access.tenantId());VectorStorevectorStore=vectorStoreRegistry.require(placement.clusterId(),placement.collectionName());SearchRequestrequest=searchRequestFactory.create(query,access,placement);returnvectorStore.similaritySearch(request);}十二、三种方案的详细对比
| 维度 | 共享Collection | 每租户Collection | 分片/混合 |
|---|---|---|---|
| 租户创建速度 | 最快 | 需创建对象 | 取决于Tier |
| 小租户资源效率 | 高 | 低 | 高 |
| 大租户性能隔离 | 弱到中 | 强 | 强 |
| 权限依赖 | 高度依赖过滤 | 依赖路由 | 过滤+路由 |
| Collection数量 | 少 | 多 | 中 |
| 索引参数定制 | 全局 | 每租户 | 按Shard/Collection |
| 单租户备份恢复 | 较难 | 容易 | 中到容易 |
| 单租户删除 | 过滤删除 | 删除Collection | 按Shard/过滤 |
| 热点迁移 | 较难 | 容易 | 设计目标 |
| 运维复杂度 | 低 | 高 | 中高 |
| 适合租户规模 | 大量小租户 | 少量大租户 | 规模差异大 |
| 物理隔离表达 | 弱 | 中强 | 可分层 |
| 故障影响范围 | 较大 | 单Collection | 可控 |
| 成本 | 最低 | 较高 | 平衡 |
十三、容量模型怎么估算
不能只看向量数量。
需要估算:
向量原始存储 +向量索引 +Payload +Payload索引 +副本 +Segment开销 +缓存 +增长冗余向量原始大小近似:
向量数量 × 维度 × 每维字节数例如:
1000万向量 × 1536维 × 4字节 ≈ 61.44 GB这还没有包括:
- HNSW;
- Payload;
- 副本;
- WAL;
- Segment;
- 元数据;
- 压缩差异。
为每租户建立独立Collection时,还要计算每个小索引的固定开销。
十四、过滤选择性为什么重要
共享Collection查询:
tenant_id == T001如果T001只有全库的0.001%,过滤选择性很高。
向量数据库需要能在过滤条件下有效搜索,否则可能:
- 扫描大量无关候选;
- 延迟波动;
- Recall下降;
- CPU升高。
因此需要:
- tenant_id索引;
- 合理HNSW过滤配置;
- 查询始终带tenant过滤;
- 对大租户考虑专用分区;
- 真实规模压测。
不要只用几千向量的开发环境判断架构。
十五、租户删除怎么设计
共享Collection
执行:
delete where tenant_id == T001风险:
- 条件错误扩大删除;
- 删除耗时;
- Segment回收延迟;
- 备份中仍存在;
- 派生缓存和引用未清理。
需要删除任务:
publicrecordTenantDeletionJob(StringjobId,StringtenantId,StringplacementVersion,DeletionStatusstatus,longexpectedVectorCount,longdeletedVectorCount){}独立Collection
可以删除整个Collection,但仍要清理:
- 数据库记录;
- 对象存储;
-缓存; - 备份;
- 审计保留;
- Placement记录;
- 连接池。
十六、单租户备份与恢复
共享Collection中恢复单租户通常更复杂:
从备份恢复临时集群 ↓ 筛选租户数据 ↓ 写回生产 ↓ 校验版本独立Collection可以直接恢复Collection。
因此,如果合同明确要求:
单租户RPO/RTO需要把恢复成本纳入选型,而不是只比较查询性能。
十七、在线迁移:小租户晋升为独立Shard
当共享租户变成热点客户,需要迁移。
推荐状态机:
ACTIVE_SHARED ↓ MIGRATION_PREPARING ↓ DUAL_WRITE ↓ BACKFILL ↓ VERIFYING ↓ READ_SWITCH ↓ DRAINING_OLD ↓ ACTIVE_DEDICATED1. 创建目标Placement
TenantVectorPlacementtarget=placementFactory.dedicatedShard(tenantId);2. 开启双写
新写入同时进入旧位置和新位置。
3. 回填历史数据
按文档版本和Chunk ID复制。
4. 校验
比较:
- Point数量;
- 文档数量;
- Checksum;
- 采样向量;
- 检索结果;
- 权限元数据;
- 索引状态。
5. 切读
Placement版本原子更新。
6. 保留回滚窗口
旧数据暂不删除。
十八、双写的幂等设计
Point ID必须稳定。
推荐:
tenant_id +logical_document_id +document_version +chunk_idHash为固定Point ID。
publicUUIDpointId(StringtenantId,StringdocumentId,Stringversion,StringchunkId){returnUUID.nameUUIDFromBytes(String.join("|",tenantId,documentId,version,chunkId).getBytes(StandardCharsets.UTF_8));}双写重试不会产生重复向量。
十九、迁移期间如何保证读取一致
三种模式:
1. 旧读+双写
最安全的开始阶段。
2. 新读+旧读对比
影子读取新位置,比较结果但仍返回旧位置。
3. 新读+旧位置回滚
验证通过后切换。
不要在回填未完成时合并新旧查询结果,容易产生重复Chunk和版本冲突。
二十、Embedding配置不同怎么办
不同Collection可能使用不同:
- Embedding模型;
- 维度;
- 距离度量;
- 量化;
- 索引参数。
Placement必须记录:
publicrecordEmbeddingProfile(StringprofileId,StringmodelId,intdimensions,DistanceMetricdistance,Stringnormalization,Stringversion){}查询必须使用与目标Collection一致的Embedding Profile。
如果路由到错误Collection,维度可能直接报错;更危险的是维度相同但Embedding模型不同,查询能执行却质量严重下降。
二十一、跨租户共享知识怎么处理
企业平台可能有:
平台公共知识 +租户私有知识不要把公共知识复制到每个租户。
可以设计:
公共Collection 租户Collection/分区查询:
公共知识检索 +租户私有检索 ↓ 分别权限校验 ↓ 结果融合证据中明确:
scope=PLATFORM_PUBLIC scope=TENANT_PRIVATE公共知识仍要版本化,不能默认永久有效。
二十二、选型决策树
是否存在物理隔离或独立集群要求? ├─ 是:独立Collection或独立集群 └─ 否:继续 租户数量是否很多,且大多数租户较小? ├─ 是:共享Collection+Payload过滤 └─ 否:继续 租户数据规模和QPS差异是否很大? ├─ 是:分层混合+大租户专用Shard └─ 否:继续 是否要求单租户独立备份恢复或索引参数? ├─ 是:独立Collection └─ 否:共享Collection 未来是否可能出现超级租户? ├─ 是:从第一天建立Tenant Placement和在线迁移能力 └─ 否:仍建议保留Placement抽象二十三、不同规模的推荐方案
100个大型租户
每租户独立Collection 或 少量租户共享+大型租户独立1万个中小租户
共享Collection +tenant Payload索引 +热点租户晋升机制100万个个人空间
共享Collection/分区 禁止每用户独立Collection 强制查询守卫 缓存与权限哈希强监管客户
独立Collection 独立密钥 独立备份 必要时独立集群二十四、生产检查清单
□ 已统计租户数量与未来增长 □ 已统计单租户向量分布而不只看平均值 □ 已明确物理隔离和恢复要求 □ 共享Collection查询强制tenant过滤 □ tenant_id已建立Payload/元数据索引 □ Tenant Placement是统一真相源 □ 业务代码不拼Collection名称 □ Placement记录Embedding Profile □ 大租户有晋升阈值 □ 在线迁移具备双写、回填和验证 □ Point ID稳定且双写幂等 □ 缓存Key包含Placement版本 □ 单租户删除和恢复经过演练 □ 公共知识与租户私有知识分开治理 □ 已按真实规模测试过滤延迟与Recall总结
多租户向量库没有一个适合所有规模的固定答案:
共享Collection 适合大量小租户和资源复用 每租户Collection 适合少量大租户、强隔离和独立恢复 分片键与混合架构 适合租户规模差异大、需要热点晋升的平台最重要的不是今天选哪一种,而是从一开始建立:
Tenant Placement目录 +稳定Point ID +权限查询守卫 +分层容量指标 +在线迁移能力这样系统才能在客户规模变化时,从共享平滑晋升到专用资源,而不是被早期Collection命名方式永久锁死。