多租户知识库怎么做?用户、团队、文档和向量数据隔离
码海寻道 · 大模型、智能体与 RAG 工程组件系列第 45 篇
多租户知识库最危险的错误,不是页面显示错了,而是用户在检索结果、引用或 Agent 工具中看到了另一个租户的数据。隔离设计必须贯穿身份、API、数据库、对象存储、向量检索、缓存和日志。
一、先定义租户模型
Tenant ├── Users ├── Teams ├── Roles └── KnowledgeBases └── Documents常见关系:
- 用户属于一个或多个团队;
- 团队拥有或共享知识库;
- 文档属于知识库和租户;
- 权限可以继承,也可以针对文档单独配置。
不要只在用户表里增加一个tenant_id就认为完成了多租户设计。真实系统还要处理跨团队协作、离职、邀请、转移和审计。
二、隔离的四个层次
认证隔离
Token 或会话中记录用户身份,但不要完全信任客户端提交的租户 ID。
授权隔离
服务端根据用户、角色、团队和资源关系计算可访问范围。
数据隔离
PostgreSQL 行级策略、对象 Key 前缀和向量过滤共同限制数据范围。
运行隔离
缓存 Key、任务队列、日志、限流和模型调用预算都要带租户维度。
三、PostgreSQL 中的租户字段
业务表通常显式保存tenant_id:
CREATETABLEdocuments(id uuidPRIMARYKEY,tenant_id uuidNOTNULL,titletextNOTNULL,statustextNOTNULL,created_at timestamptzNOTNULLDEFAULTnow());CREATEINDEXidx_documents_tenant_statusONdocuments(tenant_id,status);所有 Repository 查询都应带租户范围。可以使用 PostgreSQL Row-Level Security 增加数据库层保护,但应用层仍要正确设置当前租户上下文。
身份服务应把“用户属于哪些租户、在租户内拥有什么角色、能访问哪些知识库”作为授权事实源。请求进入业务层后生成不可由客户端覆盖的AuthContext,数据库连接、对象存储 Key、向量过滤和缓存 Key 都从这个上下文派生。
四、向量数据如何隔离?
常见方案:
同一 Collection + tenant_id
成本低、租户数量扩展容易,但每次搜索必须带可信过滤条件。
每个租户独立 Collection
隔离强,但集合、索引和运维数量随租户增加,适合少数大型租户或强隔离场景。
分区或分区键
适合稳定且数量可控的数据域。不要为每个用户随意创建 Partition。
无论采用哪种方案,向量库都不能成为唯一授权层。检索命中后仍应进行业务回查和权限确认。
同时要测试空过滤、错误字段和召回不足时的行为,确保任何检索路径都不会因为过滤条件缺失而退化为全库搜索。最终返回前仍应按chunk_id重新确认资源可见性。
五、对象存储路径隔离
tenants/{tenant_id}/knowledge-bases/{kb_id}/documents/{document_id}/v3/source.pdf服务端生成对象 Key,客户端不能自定义跨租户路径。下载使用短时预签名 URL,并在生成前检查用户权限。
六、缓存 Key 必须带租户
错误:
rag:answer:{query_hash}正确:
rag:answer:{tenant_id}:{kb_version}:{model_version}:{query_hash}会话、限流、权限范围、检索结果和任务状态都要避免跨租户复用。Redis 共享集群时尤其需要做 Key 规范和访问审计。
仅加入tenant_id还不够,问答缓存通常还要包含user_id或权限版本、知识库serving_version、模型版本、检索参数和查询哈希。角色变化、文档下线和权限回收时,应提升权限版本或主动失效,避免旧缓存绕过新授权。
七、API 权限检查流程
请求 Token ↓ 解析 user_id ↓ 服务端确定 tenant_id 和 team_scope ↓ 检查资源所属关系 ↓ 生成数据库和向量过滤条件 ↓ 执行查询 ↓ 结果再次脱敏与校验不要让前端直接控制tenant_id、team_id、document_ids或向量过滤表达式。前端可以提出目标,权限范围必须由服务端裁决。
八、跨租户操作要单独设计
平台管理员、客服和数据分析任务可能需要跨租户访问,但不能让普通业务接口自动获得这种能力。
跨租户操作建议使用独立接口、独立角色和短时授权,并要求填写工单或审批单。操作结果要带目标租户列表、原因、操作者和审批链;禁止通过修改请求头、查询参数或模型工具参数临时扩大范围。
应使用:
- 独立角色和权限;
- 明确的操作原因;
- 限定的租户范围;
- 更详细的审计;
- 必要时双人审批;
- 导出数据的脱敏和水印。
九、租户删除和数据生命周期
删除或注销租户时,需要清理:
PostgreSQL 记录 对象存储文件 Milvus / pgvector 向量 Redis 缓存和会话 消息队列中的任务 搜索和监控中的敏感副本 备份中的合规数据大规模删除应异步执行,并向管理员展示进度、失败项目和最终审计结果。不要因为界面显示“租户已删除”就假设所有副本已经同步清理。
删除任务应先写入租户级 tombstone,阻止新请求和新任务继续写入,再按依赖顺序清理 PostgreSQL、对象存储、向量索引、Redis、队列消息、日志和备份副本。清理任务要可重试、可审计,并能报告仍未删除的副本。
十、隔离测试不能只测正常请求
至少测试:
- 用户修改请求中的 tenant_id;
- 猜测其他租户 document_id;
- 通过搜索结果获取跨租户引用;
- 通过缓存命中得到其他租户答案;
- 旧任务完成后覆盖新租户状态;
- 对象存储预签名 URL 越权;
- Agent 工具调用绕过前端权限;
- 批量导出和管理员接口越权。
十一、上线检查清单
- 身份和租户范围由服务端确定;
- PostgreSQL 查询包含租户约束或 RLS;
- Milvus 检索包含可信过滤;
- 对象存储 Key 按租户隔离;
- Redis Key 包含租户和版本;
- 任务、日志和审计带租户上下文;
- 跨租户管理员操作独立授权;
- 删除、备份和恢复有明确策略;
- 已完成越权和缓存串租户测试。
结语
多租户隔离不是某个组件的单一功能,而是一条贯穿身份、权限、数据库、对象存储、向量库、缓存、任务和日志的系统边界。任何一层遗漏,都可能让前面的隔离失效。
至此,第八篇章“后端、前端与系统集成”全部完成。下一篇章将进入评估、监控与安全,从大模型应用如何评估开始。
参考资料
- PostgreSQL 官方文档:Row Security Policies
- Milvus 官方文档:Filtered Search
- OWASP:Authorization Cheat Sheet
本文为“码海寻道”原创技术文章。多租户系统涉及权限和数据合规,正式上线前应进行独立安全评审与恢复演练。