news 2026/8/7 9:59:18

单Collection、每租户独立Collection与分片键怎么选?向量数据库多租户架构完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单Collection、每租户独立Collection与分片键怎么选?向量数据库多租户架构完整指南

文章摘要

企业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 └── ...

优势

  1. Collection数量少;
  2. 索引和Segment固定开销低;
  3. 资源利用率高;
  4. 新租户无需创建基础设施;
  5. 小租户可以共享索引和缓存;
  6. 统一备份和监控;
  7. 适合大量长尾小租户。

风险

  1. 过滤条件错误会造成跨租户泄露;
  2. 高基数租户字段需要合理索引;
  3. 大租户可能占用大量缓存和CPU;
  4. 删除单租户数据需要精准过滤;
  5. 单Collection故障影响范围大;
  6. 单租户独立恢复更困难;
  7. 全局索引参数难以适配所有租户。

三、共享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 query

4. 数据完整性约束

每个Chunk必须包含:

  • tenant_id;
  • document_id;
  • status;
  • version。

5. 越权测试

持续执行跨租户蜜罐查询。

6. 热点治理

按租户统计:

  • QPS;
  • 向量数量;
  • 查询延迟;
  • CPU;
  • 缓存命中;
  • 写入速率。

达到阈值后可迁移到独立分片或Collection。

五、方案二:每租户独立Collection

每个租户拥有自己的Collection:

tenant_T001_knowledge tenant_T002_knowledge tenant_T003_knowledge

优势

  1. 数据边界直观;
  2. 查询无需tenant过滤;
  3. 单租户删除简单;
  4. 单租户备份和恢复更容易;
  5. 可设置独立索引参数;
  6. 大客户性能更可控;
  7. 合规和专属资源表达更清晰;
  8. 租户迁移可独立执行。

风险

  1. Collection数量可能爆炸;
  2. 每个Collection有索引和Segment开销;
  3. 大量小Collection浪费内存;
  4. 创建、优化、备份和监控复杂;
  5. 元数据变更需要批量操作;
  6. 平台升级要处理海量对象;
  7. 查询路由依赖准确的Collection目录;
  8. 跨租户共享知识更难。

六、为什么“每租户一个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

优势

  1. 保留统一Collection管理;
  2. 大租户可独立Shard;
  3. 查询只访问目标Shard;
  4. 热点隔离;
  5. 小租户仍共享资源;
  6. 支持租户晋升;
  7. 更适合租户规模差异显著的系统。

风险

  1. 需要维护Tenant→Shard映射;
  2. 分片数量不能无限增长;
  3. 迁移过程复杂;
  4. 双写和一致性要求高;
  5. 不同数据库能力差异较大;
  6. 运维与容量规划要求更高。

九、为什么分片键不能直接使用所有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_DEDICATED

1. 创建目标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_id

Hash为固定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命名方式永久锁死。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 9:59:11

Unity开发中VS编译失败:解决AssetImportWorker文件占用问题

1. 项目概述&#xff1a;当VS遇上Unity&#xff0c;文件锁引发的“血案” 如果你是一名使用Visual Studio&#xff08;后文简称VS&#xff09;作为主力IDE的Unity开发者&#xff0c;那么下面这个场景你一定不陌生&#xff1a;在Unity编辑器中修改完一个C#脚本&#xff0c;满怀期…

作者头像 李华
网站建设 2026/8/7 9:58:23

Unity游戏完全汉化终极指南:从资源探查到字体集成的完整工作流

1. 项目概述&#xff1a;为什么我们需要一份“终极”汉化指南&#xff1f; 如果你是一名独立游戏开发者&#xff0c;或者是一个对某款Unity游戏爱不释手、却苦于没有官方中文的玩家&#xff0c;那么“汉化”这个词对你来说一定不陌生。市面上关于游戏汉化的教程和工具零零散散&…

作者头像 李华
网站建设 2026/8/7 9:49:10

SPI协议深度解析:从时钟极性/相位到多从机管理实战

1. 项目概述&#xff1a;为什么SPI值得你花时间彻底搞懂&#xff1f; 搞嵌入式开发这么多年&#xff0c;我敢说SPI&#xff08;Serial Peripheral Interface&#xff09;是除了GPIO之外&#xff0c;工程师打交道最多的通信协议之一。从驱动一块小小的OLED屏幕&#xff0c;到读写…

作者头像 李华
网站建设 2026/8/7 9:48:50

Unity InputField焦点与光标控制优化:解决UGUI输入框交互难题

1. 项目概述&#xff1a;为什么InputField的焦点与光标控制如此棘手&#xff1f; 在Unity里做UI交互&#xff0c;InputField&#xff08;输入框&#xff09;组件绝对是高频使用的控件之一。无论是登录注册、聊天框&#xff0c;还是游戏内的道具命名、数值输入&#xff0c;都离不…

作者头像 李华