ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多租户RAG架构设计:隔离、共享与知识形态的平衡

多租户RAG架构设计:隔离、共享与知识形态的平衡 1. 为什么我放弃了现成方案决定自研一个多租户 RAG如果你正在做企业内部知识助手或者准备给不同客户交付一套带知识库问答的 SaaS 产品大概率会撞上同一个问题多个租户的知识要隔离但 GPU 和向量库资源不可能每个租户都复制一套。这个项目做完之后我最大的感慨是——多租户 RAG 不是给 RAG 加一个 tenant_id 字段就完事它牵动存储、索引、检索链路、权限模型乃至部署形态的整套设计。UniRAG 是我在团队内部从零搭起来的一套多租户 RAG 框架这篇文章把整个设计过程、取舍逻辑和踩坑细节一次讲透。先交代一下背景。我们团队做的事是给不同客户提供知识库问答能力客户之间数据完全隔离每个租户可能有自己的文档、表格、甚至知识图谱。早期我们直接在一套开源 RAG 框架上改往向量表里塞租户字段看起来能用但等到租户数量上来、知识量上来各种问题就爆发了向量检索召回串数据、索引更新互相拖累、某个大租户跑批把全站请求拖到超时、不同租户对知识的形态需求根本不一样——有的租户就是 PDF 文档有的租户想直接查结构化数据库还有的租户手里是一堆带关系的业务实体。这时候你才意识到所谓多租户 RAG真正要解决的是三个层面的事情数据隔离、算力共享、知识形态的统一。1.1 多租户到底卡在哪RAG 的三大瓶颈先说清楚 RAG 本身当前的瓶颈否则后面对设计的讨论没有依据。第一个瓶颈是知识库的碎片化。传统 RAG 把文档切块塞进向量库切完就完了块与块之间没有关系碰到需要跨文档推理的问题就露馅。这也是为什么圈子里现在都在聊构建类 KG 的知识库、为什么有人非要用图谱补一层。我在调研阶段看了不少 rag 瓶颈相关讨论大家的共识基本一致单靠向量相似度支撑不起复杂问答。第二个瓶颈是检索质量的不可控。召回的 TopK 里经常混进不相关片段重排序模型能缓解但没法根治而且重排序本身也是资源消耗大户。更麻烦的是检索质量很大程度上取决于 chunk 策略和 embedding 模型这两样东西恰恰是多租户场景下最难统一的——不同租户的知识领域差异很大一个法律知识库和一个电商客服知识库最优的 chunk 粒度完全不一样。第三个瓶颈才是多租户特有的隔离与共享的矛盾。隔离指的是租户数据不能互相看到共享指的是 embedding 模型、向量库、GPU 推理这些资源最好大家共用。租户少的时侯直接给每个租户开一套独立 RAG 实例就行但租户一多资源开销、运维复杂度、索引更新成本都会线性膨胀。UniRAG 的设计初衷就是想找到一个一套底座、多租户共用、但逻辑上完全隔离的平衡点。1.2 团队内部的方案对比dify 社区版、开源框架与自研既然要做选型我把市面上能走的路都趟了一遍。第一类是 dify 社区版这类平台型工具。dify 社区版 1.10 确实开始支持多租户能力但它的多租户更多是工作空间级别的隔离每个空间有自己的知识库、应用和 API Key。好处是上手快坏处是对于我们要交付给客户做私有化定制的场景dify 的抽象层级太固定底层的检索链路、知识库类型、权限模型很难按客户需求调整。如果你只需要快速搭一套内部工具dify 足够如果要做产品化就会撞上定制天花板。第二类是各种开源 RAG 框架包括一些所谓的企业级 RAG。它们的问题出在两个地方一是多租户支持基本靠全网搜一下 rag 知识库怎么搭那种单租户教程框架层面很少内置租户路由和资源配额二是知识库形态单一要么纯文档向量检索要么接一个外部图数据库做 GraphRAG像我们需要的文档 结构化数据 图谱三种形态统一在一个问答入口里几乎没有现成方案。第三类才是自研。这也是 UniRAG 项目的真正起点。我们定的调子是不重复造 RAG 轮子检索、重排、生成还是用成熟组件但存储隔离、租户路由、知识库模型、权限注入这几层必须自己设计。1.3 UniRAG 的目标边界哪些功能做哪些坚决不做任何自研项目最怕的就是范围失控。UniRAG 立项时我就定了三做三不做。做租户级的数据隔离与权限过滤、文档/结构化/图谱三类知识库的统一建模、共享资源池下的性能隔离。 不做不重新训练 embedding 模型不做分布式向量库初期直接用成熟的向量数据库把分布式交给云厂商不做完整的 RAG 工作流编排那又变成 dify 了。这个边界很重要。很多人自研 RAG 项目最容易犯的错就是想从头到尾全自研结果半年过去连检索都还不稳定。我的经验是非核心链路尽量用成熟组件把精力集中在多租户这个真正的差异化点上。2. 租户隔离的第一道防线向量库与元数据策略多租户 RAG 的第一道设计决策发生在存储层。你要回答一个核心问题租户数据在向量库里到底怎么放放的方式直接决定检索时能不能高效隔离、索引更新时会不会互相影响。2.1 向量库选型collection 隔离、分区隔离还是 metadata 过滤市面上的向量数据库大致给出三种隔离粒度。第一种是 collection 级隔离一个租户一个 collection隔离最彻底但 collection 数量一多后端分片管理和内存开销就上来了。第二种是分区隔离同一张表按租户分区比 collection 轻一些但能不能做到依赖具体产品的实现。第三种是 metadata 过滤所有租户的数据在同一张表里靠向量记录上的租户标签做检索时过滤。我在项目里做了个简单测试对比当租户数量在 50 个以下时三种方案性能差距很小到了 200 个租户、每个租户平均 10 万份文档块时metadata 过滤方案的召回精度问题开始暴露——过滤条件写得太粗会把别的租户的数据扫进来写得太细则过滤本身变成一次全表扫描。最终 UniRAG 用的是collection 为主、metadata 为辅的组合每个租户默认一个独立的向量 collection确保数据物理隔离但在需要跨租户检索的场景比如运营后台做全局问答分析才用 metadata 过滤走一个只读的聚合索引。这个取舍放弃了一套索引服务所有租户的极致节省换来了最不容易出事的隔离边界。2.2 租户元数据的数据模型设计存储结构定了接下来是租户元数据模型。很多项目在这里只保存一个 tenant_id后面想做高级功能全部抓瞎。UniRAG 的租户元数据分成三层基础层租户 ID、租户名称、状态启用/停用/欠费冻结、创建时间。 资源配置层分配的向量 collection 名称、允许使用的知识库类型列表、模型路由组 ID后面检索生成会用不同的模型、配额上限文档总数、索引大小、每分钟请求数。 策略层检索 TopK 上限、是否允许跨知识库检索、重排序模型的开关、以及权限过滤规则。这里我特别想强调资源配置层的重要性。多租户系统最怕的就是某个租户把共享资源池打爆。我在设计时给每个租户定了三档配额——基础档、标准档、高并发档配额信息缓存在 Redis 里请求进来先查配额再查数据两个逻辑分开。这套设计在压力测试阶段帮了大忙后面压测复盘部分会详细说。2.3 共享索引与租户路由如何做到全租户召回但零串租存储层还有一个容易忽略的点向量索引的更新策略。在多租户场景下你不能让某个租户的大批量文档导入任务把整个搜索服务拖垮。UniRAG 的做法是异步索引队列——当某个租户上传新文档先把文档写入对象存储返回处理中状态后台任务负责解析、切片、embedding、写入该租户的 collection。每个租户的索引任务占一个独立队列配合信号量控制并发这样大租户跑批不会影响小租户的实时检索。租户路由则放在接入层。每一次问答请求都会携带租户身份经过网关层解析后请求被路由到该租户对应的检索链路口袋。路由信息不是简单查表而是一段配置化的检索计划内容包括从哪个 collection 取向量、要不要走额外关键词检索、要不要查图谱子图、用什么重排序模型。这套路由设计的价值在后期做不同租户的 A/B 实验时体现得淋漓尽致——只要改一段配置就能让特定租户走新的检索链路而不用动核心代码。3. 知识库建模的取舍文档知识库、结构化知识库与图谱KG知识库RAG 项目做到中期基本都会遇到同一个坎用户不满足于文档问答开始问为什么这两个数据对不上这个客户的上一个订单和投诉记录有什么关系。这些问题的答案不在 PDF 里而在结构化数据里在实体关系里。这就是 UniRAG 知识库建模要解决的领域。3.1 三类知识库的定位差异与典型应用场景先把概念理清。rag 知识库通常指传统的文档型知识库把 PDF、Word、Markdown 切片成向量块适合根据文档内容回答问题结构知识库指表格、数据库、API 里的结构化记录适合精确查询、聚合统计、条件筛选kg 知识库知识图谱库则围绕实体和关系组织知识适合多跳推理、关联分析、路径查询。它们的应用场景差异非常明显。比如同样是回答这个客户的账户状态是否正常文档知识库大概率会从一份服务协议 PDF 里找出账户状态的笼统描述结构化知识库能直接查出该客户的账户表记录图谱知识库则能沿着客户-订单-售后工单-客服的关系链给出上下文丰富的答案。所以问题不是哪种知识库更好而是如何让一套问答系统按问题类型自动选择正确的知识源。我在 UniRAG 里做了一个知识路由模块请求进来后先做一次轻量级意图判断——是事实型问题、统计型问题、还是关系推理型问题——再决定走哪个知识库通道。这个判断不一定每次都对但结合下面的混合检索设计能有效提升整体回答质量。3.2 如何在 UniRAG 里统一三类知识源的加载与解析不同知识库的加载链路差异很大。文档知识库需要解析、清洗、切片、embedding结构化知识库需要建表映射、配置查询模板图谱知识库需要从文本或结构化数据里抽取实体和关系。UniRAG 的做法是定义统一的知识源适配器接口每个适配器负责把一种数据源转换成标准的知识单元。文档适配器输出向量块结构化适配器输出表 查询模板 自然语言到查询语句的转译规则图谱适配器输出实体节点 关系边 子图查询模板。统一的接口让上层检索模块不需要关心底层数据源是什么只需要调用统一的知识查询接口。这里我给一个实际的适配器配置伪代码方便理解data_source: type: structured_table engine: mysql connection: ${TENANT_DB_CONN} tables: - name: customer_order primary_key: order_id fields: - order_id - customer_name - amount - status query_templates: - intent: query_order_status sql: SELECT status FROM customer_order WHERE order_id ? - intent: sum_order_amount sql: SELECT SUM(amount) FROM customer_order WHERE customer_name ?类似地文档适配器和高图适配器各自生成自己的知识单元描述最终都注册到租户的知识源目录里。知识路由模块在检索时根据目录来决定走哪些通道。3.3 Ontology RAG 的落地图谱路由与查询改写热词里提到的 ontology rag 值得单独说一段。ontology本体是知识图谱的骨架定义了实体类型、属性、关系类型以及约束规则。传统的 RAG 图谱方案往往只做实体抽取和关系存储检索时直接查子图但这样有个问题——用户问哪些客户在最近一个月投诉过两次以上这个查询涉及客户、投诉工单、时间三种实体以及聚合统计逻辑纯子图遍历做不了必须先经过本体层做语义映射。UniRAG 在 Ontology 上的落地分三步。第一步是根据租户的业务领域定义本体模型比如电商租户定义客户、订单、退货、售后工单这几类实体以及它们之间的创建、包含、关联关系。第二步是使用本体模型指导实体抽取——抽取时不仅抽实体名还记录它属于哪个类型、具备哪些属性。第三步是查询改写当知识路由判定一个问题需要走图谱通道时会先生成大致的图查询语言类 Cypher 语法再结合本体的约束关系做校验和改写。比如说用户问去年退货率最高的商品是什么系统先改写为查找商品实体统计其关联退货单数量按时间过滤排序然后才去执行图谱查询。这套流程的精准度比直接向量检索高很多代价是每个租户需要花时间构建本体模型。对于业务模式清晰的企业客户这个投入值得因为问答质量提升是质变级别的。4. 混合检索链路多租户下的权限注入与召回排序知识源统一了真正的硬骨头在检索链路。多租户 RAG 的检索链路不是向量召回 重排这么简单你要同时处理多知识源召回、权限过滤、租户级策略注入还要保证延迟可控。4.1 检索链路里的四路召回设计UniRAG 的检索链路做了四路召回向量召回、关键词召回、结构化查询召回、图谱子图召回。这四路并行执行各自返回候选结果最后合并统一送给重排序模块。为什么需要四路而不是一路向量召回因为不同问题适合不同的召回方式。事实型问题向量召回效果好含精确名称、编号、ID 的问题关键词召回更可靠统计型问题必须走结构化查询关系推理型问题则依赖图谱。四路召回的设计参考了经典混合检索思路但实现里有不少多租户特有的细节比如每路召回都要带上租户权限上下文向量召回只能从该租户的 collection 里取数关键词召回只能搜索该租户的倒排索引图谱子图查询只能访问该租户在本体实例数据里的子图。这里我给大家一个检索链路的示意图用文字描述不画图网关层拿到请求 → 路由模块确定租户与检索计划 → 四路并发召回 → 各召回结果统一格式化为标准候选列表 → 进入重排序器 → 生成模块拼接上下文 → 输出答案。每一步之间都有超时控制和日志埋点。4.2 权限过滤应该放在召回前还是召回后这是我在设计过程中纠结最久的一个问题。权限过滤放召回前即查询时就限定数据范围的好处是效率高、结果干净坏处是检索逻辑里到处要带上权限条件复杂度直线上升。权限过滤放召回后召回完再统一过滤的好处是检索逻辑简单坏处是可能召回大量无权访问的数据既浪费计算又存在数据泄露的窗口期。我的最终决定是强隔离权限前置弱隔离权限后置。所谓强隔离就是租户级别的数据隔离这个必须前置从源头保证不同租户的数据不会进入候选集所谓弱隔离是租户内部的文档级权限比如某个部门的文档只对特定角色可见这个放在召回后过滤。原因是弱隔离条件往往动态变化如果全部前置索引条件会极不稳定反而影响检索质量。这个分层设计在实际项目里运行得很稳既保证了安全的底线又没让检索链路被权限判断拖垮。4.3 重排序模型与租户级个性化一个被低估的优化点很多 RAG 项目把重排序当成一个可选优化但在多租户场景下重排序是租户级个性化最重要的落点。不同租户的知识领域、用户群体不一样同一段候选内容对不同租户的相关性排序可能完全不同。UniRAG 允许多个重排序模型同时启用按租户配置路由。默认租户可以共用同一个通用重排序模型但对检索质量要求高的租户可以单独部署一个基于行业数据微调过的重排序模型。配置方式仍然是一段路由规则。这样做的成本收益非常清晰通用模型保障底线专用模型提升上限而这两者之间不需要改一行检索代码。另外还做了一个租户级语义缓存。同一租户的相似问题在短时间内会被大量重复提问典型如企业内部制度问答如果每次都走完整四路召回和重排序资源浪费很大。UniRAG 在缓存设计上做了两层第一层是租户内常见问题的完全匹配缓存命中直接返回第二层是语义缓存用 embedding 相似度判断两个问题是否等价相似度超过阈值就直接复用之前的回答。这个设计使某些高频租户的问答延迟从 4 秒降到 0.5 秒以内效果非常立竿见影。5. 部署上线与压测复盘从 Mac 本地到线上稳定理论设计再多最后都要落到部署这一步。UniRAG 在整个开发过程中的部署迭代对我们帮助很大这里把从本地到线上的完整路径和压测后的调整写出来也算是最有实操参考价值的部分。5.1 在 Mac 上先跑通单机版依赖选择与快速启动很多人问怎么在 Mac 上搭建 rag 知识库或者完整的 RAG 项目。如果你只是先跑通 UniRAG 的单机版做功能验证依赖清单其实不复杂一个向量数据库我用的是单机模式跑在 Docker 里、一个嵌入模型服务本地加载开源 embedding 模型、一个 LLM 推理服务可以接入 OpenAI 兼容接口也可以本地部署、再加一个 Redis 做缓存和配额存储。单机版我只在 Docker Compose 里编排向量库和 Redis模型服务直接跑在宿主机上方便调试。这个阶段最重要的不是性能而是把租户路由逻辑调通。我建议第一次跑通时只创建一个测试租户上传一份文档跑通文档加载 → 切片 → 向量化 → 检索 → 生成的最小闭环再逐步加第二、第三个租户测试隔离性。5.2 线上部署的细粒度控制索引更新、缓存与并发限流从单机版到线上部署最大的变化是要考虑多租户的并发争抢。线上我做了三个层面的控制。索引更新控制上面提过每个租户的索引任务走独立队列这里再补充一个细节——队列的消费者数量按租户配额动态调整。大租户可以配置 4 个消费者同步处理小租户 1 个就够避免后台任务占用过多 CPU 和内存。缓存控制语义缓存按租户分片存储过期策略也按租户配置。比如企业制度问答类租户缓存 TTL 设 24 小时金融数据类租户数据更新频繁缓存 TTL 缩短到 15 分钟并且监听数据更新事件主动失效。并发限流网关层统一做租户级限流采用令牌桶算法。这里有个关键经验——限流一定要按租户的配额做而不能只看全局 QPS。只看全局的话某个租户的突发流量会把其他租户的请求全部挤掉。UniRAG 的限流方案是全局 QPS 限制兜底租户级 QPS 限制独立控制租户超限时返回特定的限流错误码让前端可以提示当前知识库访问繁忙。5.3 压测后的三个调整索引重建、缓存失效与租户级超时压测阶段我们暴露了三个问题每个都值得展开。第一个问题是索引长时间运行后性能衰减。压测跑了一周某大租户的向量 collection 写入量很大检索延迟从 200ms 涨到 800ms。排查后发现是向量索引的段合并没有跟上写入速度。解决方式不是调段合并参数而是设计了一个定时索引重建机制每天凌晨对数据量大、写入频繁的租户执行一次索引优化。这里我给新手一个建议别等问题出现才去看索引性能要按租户的数据增量做索引健康度监控。第二个问题是缓存失效风暴。有一次某个租户批量更新了知识库触发了大量缓存失效瞬间后端查询压力暴增导致一串请求超时。这个问题的根因是缓存失效没有做错峰。修改后的方案是缓存失效通知先发到消息队列由消费者分批、错峰执行失效操作同一租户的失效操作串行执行绝不允许所有缓存同时清空。第三个问题是租户级超时设置。最开始所有租户的检索超时都是同一个值结果一个小租户的图谱查询因为数据量小反而更容易超时——倒不是性能问题而是图谱子图查询的冷启动延迟。后来我把超时改为基础超时 租户数据量系数的公式计算并且给每个租户单独配置超时阈值。这个公式比较简单直接timeout_ms 800 min(2000, tenant_doc_count / 10000 * 200)这条公式的意思是租户文档量越大给的超时时间越多但最高不超过 2800ms。这个经验性公式未必普适但它的思路是对的——多租户系统的超时配置不能一刀切要跟着租户规模走。6. 说点个人项目体会最后分享几个我做 UniRAG 这个项目过程中反复验证过的体会。第一个体会是多租户设计永远要先想清楚隔离的底线在哪。如果租户数据泄露是零容忍的那么存储层的物理隔离就不能省如果想降低成本必须在共享池上做文章那就必须在网关和检索链路里把权限注入做扎实。这两个方向没有对错但不能模棱两可、两头都想要。第二个体会是混合检索是个慢工出细活的工程。不要指望四路召回一次就能调好我在项目里花了大量时间做的是按租户、按问题类型去对比不同召回路数的效果。后来我把对比过程做成了可视化工具每次调整后能直接看到不同租户的指标变化效率才提上来。第三个体会是 RAG 项目的扩展性比想象中重要。今天你只做文档问答明天客户就会要结构化数据查询后天就会提单子图推理。如果你一开始的知识库抽象层设计得好后面接新数据源就是写一个适配器的事如果当初图省事把所有知识都塞进向量库后面重构的代价会大得多。对于想跑通类似多租户 RAG 项目的朋友我的建议是先用文档知识库跑通最小闭环再逐步加结构化数据源最后再上图谱。每加一类知识源都要确保检索质量确实提升而不是为了炫技增加复杂度。这个项目做下来我最大的体会是多租户 RAG 的难点从来不在某个单点技术上而在隔离、共享、知识形态这三个维度的平衡上。
返回列表