建筑学

多租户 SaaS 架构:CTO 需要了解什么

夜晚的地球显示互联的数字网络

对 5,000 多个租户或种子阶段产品使用具有行级安全性 (RLS) 的共享表; 每月总共花费 15-200 美元。 对于租户数量少于 100 个的受监管行业(金融科技、医疗保健)使用每个租户数据库。 对需要适度隔离的 50-5,000 个租户使用架构分离。 这一决定将影响您多年的成本结构、合规性故事和部署管道。

您正在构建 SaaS 产品。 多个客户将使用它。 每个客户都希望他们的数据保持私密,他们的配置保持独立,并且他们的体验感觉像自己的平台。 问题不在于您是否需要多租户。 关键是选哪个型号。

这一决定多年来影响着您的数据库架构、部署管道、合规性故事和成本结构。 如果出错,您将花费六个月的时间迁移到不同的模型,而您的路线图却停滞不前。

有三种模型值得考虑。 每种方法都以不同的方式权衡成本、隔离性和操作复杂性。

三种多租户架构模型

1. 每个租户数据库

每个租户都有自己的数据库。 应用程序层根据租户标识符将查询路由到正确的数据库,通常从子域、API 密钥或 JWT 声明解析。

这是最强的隔离模型。 租户 A 的数据与租户 B 的数据位于不同的数据库中。 查询错误无法跨租户泄漏行,因为这些行存在于不同连接上的不同数据库中。

优点:

  • 合规友好。 审计员喜欢听到“每个客户都有自己的数据库”。 SOC 2、HIPAA 和数据驻留要求变得简单明了。
  • 每租户备份和恢复。 当租户要求您回滚到昨天的状态时,您可以恢复一个数据库。 无需从共享桌子上进行手术提取。
  • 没有吵闹邻居的风险。 运行昂贵的分析查询的租户不会降低其他租户的性能。
  • 每个租户的扩展。 您可以将高价值租户放在更大的数据库实例上。

缺点:

  • 昂贵的。 每个数据库都有一个基准成本:计算、存储、备份、监控。 如果有 10 个租户,这是可以管理的。 500 美元时,这个项目会让您的 CFO 感到不舒服。
  • 迁移地狱。 架构更改需要跨数百个数据库运行。 您需要工具来编排迁移、跟踪哪些数据库是最新的,以及在部署过程中处理故障。
  • 连接池变得复杂。 您的应用程序服务器需要连接数百个数据库的连接池。 连接限制先于 CPU 或内存成为限制。
  • 跨租户查询很痛苦。 显示跨租户数据的聚合报告、全平台分析或管理仪表板需要联合查询或单独的分析管道。

最适合:具有严格数据驻留要求的金融科技、医疗保健和企业 SaaS。 如果您的合同规定“客户数据必须驻留在特定的 AWS 区域”或“数据必须在合同终止后 24 小时内删除”,则每个租户数据库使这两个目标都可以轻松实现。

2.共享数据库、schema分离

一个数据库,但每个租户都有自己的架构(命名空间)。 在 PostgreSQL 中,这意味着每个租户在自己的模式名称下都有一组单独的表:tenant_abc.userstenant_xyz.users。 应用程序在每个连接上设置 search_path 以将查询路由到正确的架构。

这是中间立场。 您可以获得比共享表更好的隔离,并且比单独数据库的成本更低。

优点:

  • 比每个租户数据库便宜。 1个数据库实例,1个连接池,1个监控栈。
  • 体面的隔离。 模式提供命名空间边界。 一个模式中配置错误的查询无法访问另一模式的表(假设您正确设置 search_path )。
  • 通过 pg_dump --schema 可以进行每租户备份。
  • 迁移比每个租户数据库更容易。 您可以为每个模式运行一次迁移,但所有模式都位于同一个数据库中,因此工具更简单。

缺点:

  • 模式漂移。 当迁移在某些架构上失败但在其他架构上成功时,最终会导致租户运行不同版本的架构。 调试这个就惨了。
  • PostgreSQL 不能很好地处理数千种模式。 经过 5,000-10,000 个模式,您将在 pg_catalog 查找中遇到性能下降、pg_dump 时间变慢以及 autovacuum 争用的问题。
  • 与共享数据库相同的噪声邻居风险。 一个租户的昂贵查询仍然会竞争相同的 CPU 和 I/O。
  • 工具支持不一致。 ORM 和迁移框架对每租户架构模式提供不同级别的支持。

最适合:拥有 50-5,000 个租户的中端市场 SaaS,您需要比行级安全性更好的隔离,但无法证明单独数据库的成本合理。

3. 通过行级安全性共享所有内容

所有租户共用同一张桌子。 每个表上的 tenant_id 列标识哪个租户拥有每行。 PostgreSQL 中的行级安全性 (RLS) 策略强制查询只能查看属于当前租户的行。

这是不向受监管企业销售的 B2B SaaS 产品的最常见模式。

优点:

  • 最便宜的基础设施成本。 一个数据库,一组表,一个连接池。 添加租户的额外基础设施成本为零。
  • 最简单的迁移。 一种模式,一次迁移运行。 您没有跨多个数据库或模式编排任何内容。
  • 一个代码库路径。 没有“我正在与哪个数据库交谈”或“我应该使用哪个模式”的条件逻辑。 tenant_id 列是架构的一部分,RLS 处理其余部分。
  • 跨租户分析很简单。 管理仪表板和平台范围的报告使用提升的权限查询相同的表。

缺点:

  • 查询错误可能会泄露数据。 如果开发人员编写绕过 RLS 的查询(使用超级用户连接,或忘记设置会话变量),租户数据就会泄漏。 这是代码级风险,而不是基础设施级保证。
  • 合规对话更加困难。 对于企业安全团队来说,“所有客户数据都位于同一个表中,由一列分隔”比“每个客户都有自己的数据库”更难。
  • 这里吵闹邻居的风险最高。 一个租户导入 1000 万行,锁定会影响所有租户的表。
  • 每个租户的备份和恢复需要进行手术提取。 如果不编写自定义工具,您就无法恢复单个租户。

最适合:企业级以下的 B2B SaaS。 拥有数百或数千租户的产品,其中基础设施成本比合规性认证更重要。

对照表

因素每个租户数据库模式分离共享+RLS
每个租户的成本高(专用计算+存储)中等(共享计算,单独模式)低(一桌一排)
数据隔离最强(独立数据库)中等(架构边界)最弱(专栏+政策)
查询复杂度每个租户低,跨租户高每个租户较低,跨租户中等低(相同的表,RLS 可以处理)
迁移难度硬(需要迁移 N 个数据库)中等(N 个模式,一个数据库)简单(一种模式,一次迁移)
合规准备情况非常好(审计师喜欢它)好(可防御边界)足够(需要 RLS 审核跟踪)
吵闹邻居风险没有任何展示最高
租户入职成本需要配置架构创建+迁移插入一行

我们如何在共享数据库多租户上构建 DropTaxi

当我们建造滴滴出租车,一个为印度运营商提供的多租户出租车预订 SaaS,我们选择了带有 tenant_id 列的共享数据库模型。

推理很简单。 出租车运营商是小型企业。 他们没有要求数据库级隔离的合规性要求。 租户数量需要从 5 扩展到 500 多个,且无需为每个租户增加基础设施成本。 入职必须是即时的:注册、配置品牌、指向域名、上线。 无需部署、无需配置、无需等待。

该架构在实践中的样子如下:

零部署租户入门。添加新租户意味着在 tenants 表中插入一行,其中包含其品牌名称、颜色、徽标 URL、域名、票价和 Telegram 机器人令牌。 没有 CI 管道运行。 没有容器重新启动。 对该域的下一个请求将解析新租户并呈现其品牌网站。

通过中间件的品牌子域。每个传入的 HTTP 请求都会到达读取 Host 标头的 Hono 中间件层。 中间件查询数据库(通过 Turso 上的 Drizzle ORM)以查找与该域匹配的租户。 如果找到匹配项,租户的完整配置将加载到请求上下文中。 如果没有,请求将收到 404 错误。然后,Astro SSR 层使用租户的品牌呈现页面,因此访问者看到的是独立的出租车预订网站,而不是通用平台。

所有租户的单一部署。一台 Fly.io 机器运行整个平台。 一个数据库。 一个代码库。 每个租户的唯一成本是 DNS 配置和数据库中存储其设置的行。

该模型之所以有效,是因为该产品不服务于受监管的行业,数据敏感性较低(预订详细信息,而不是财务记录),并且主要的扩展问题是租户数量而不是每个租户的数据量。

多租户系统的实用模式

无论您选择哪种模型,这些模式都会出现在生产多租户系统中。

租户解析中间件

租户解析应该在请求管道的边缘发生一次,并且解析后的租户应该在整个请求生命周期中传播。 常见的解决策略:

  • 子域名:acme.yourapp.com 解析为 acme 租户。 从 Host 标头解析。
  • 自定义域:app.acme.com 通过域查找表映射到租户。
  • 路径前缀:yourapp.com/acme/dashboard 从 URL 中提取租户。 在生产中不太常见,但在开发过程中很有用。
  • JWT/API 密钥:对于 API 优先的产品,租户标识符位于身份验证令牌中。 中间件验证令牌并提取租户声明。

将解析的租户存储在请求范围的上下文中(Hono 的 c.set()、Express 的 req.tenant 或 Go 中的线程局部变量)。 下游代码不需要重新解析租户。

连接池

在每租户数据库模型中,连接池成为您将遇到的第一个瓶颈。 每个租户数据库都需要自己的池,并且您的应用程序服务器可以保持打开的连接数量有限。

适用于生产的解决方案:

  • 每个数据库实例的 PgBouncer具有交易级别的池化。 这将许多应用程序连接复用在较少数量的数据库连接上。
  • 惰性池初始化。不要为过去一小时内未收到请求的租户创建连接池。 按需启动池并使用 TTL 驱逐空闲池。
  • 托管池服务例如 Supabase 的连接池程序或 Neon 的无服务器驱动程序,它们在应用程序进程之外处理池管理。

对于共享数据库模型,单个连接池可以正常工作。 在每个连接签出时,租户上下文在会话级别设置(对于 RLS 为 SET app.current_tenant,对于模式分离为 SET search_path)。

租户范围的缓存

您的缓存键需要租户前缀。 如果您缓存“仪表板数据”键而不将其范围限定为租户,则您将向租户 B 提供租户 A 的仪表板数据。这听起来很明显,但这是生产中最常见的多租户错误。

使用 tenant:{tenant_id}:resource:{resource_type}:{resource_id} 等密钥格式。 如果您使用 Redis,请考虑针对较小的部署为每个租户使用单独的 Redis 数据库(默认情况下可用 0-15 个),或者为较大的部署添加键前缀。

后台作业隔离

后台作业(电子邮件发送、报告生成、数据导入)需要将租户上下文从排队站点传播到工作人员。 将作业加入队列时,请在作业负载中包含 tenant_id。 在处理作业之前,工作人员应设置 HTTP 中间件提供的相同租户上下文。

对于作业队列中的嘈杂邻居保护,请为每个租户使用单独的队列或队列优先级。 导入 100,000 条记录的租户不应阻止其他租户的欢迎电子邮件。 BullMQ、Sidekiq 和 Celery 都支持命名队列,让您可以将大容量租户路由到专门的工作人员。

多门户作为多租户变体

多租户不仅仅意味着“相同的应用程序,不同的客户”。 它还可以表示“同一平台、具有不同门户的不同用户角色”。 当我们建造热情AMC,一个基于角色的多门户平台,该架构共享相同的数据库和代码库,但向不同的用户类型(管理员、经理和现场代理)公开不同的界面。 每个门户都有自己的路由、权限和 UI,但都来自具有行级范围的同一数据层。

当您的“租户”不是单独的组织而是一个组织内的单独角色时,这是一种有用的模式。 多租户原语(范围数据访问、每角色中间件、上下文传播)保持不变。

决策框架:如何选择模型

正确的模型取决于三个变量:合规性要求、预期租户数量和基础设施预算。

从合规性开始。如果您的客户属于受监管行业(金融、医疗保健、政府),或者您的合同包含数据驻留条款,则每租户数据库是最安全的选择。 成本溢价是企业合同中客户期望支付的一项。

租户数量因素。如果您预计租户数量少于 100 个,并且每个租户都会产生可观的收入,则每个租户数据库是可行的。 在 100 到 5,000 之间,如果您使用 PostgreSQL 并且可以投资迁移工具,则模式分离可以发挥作用。 超过 5,000 人时,与 RLS 共享桌子是务实的选择。 其他模式的基础设施经济学在租户数量较多时就会崩溃。

检查你的预算。如果您处于收入前或种子阶段,与 RLS 共享表格可以让您发货更快、花费更少。 当企业客户要求(并为此付费)时,您可以稍后迁移到更强大的隔离模型。 大多数 SaaS 产品永远不需要进行这种迁移,因为大多数 SaaS 产品销售给关心功能而不是数据库隔离模型的中小企业。

考虑混合方法。某些产品为其标准层运行共享表,并为企业客户运行每个租户的数据库。 这会增加操作复杂性,但它可以让您服务两个市场,而无需强制使用单一模型。 Stripe 和 Notion 都使用这种模式的变体。

要避免的常见错误

  • 为将拥有数千个租户的产品选择每个租户数据库。管理数千个数据库的运营成本超过了隔离带来的好处。 如果您的租户是每月支付 50 美元的中小型企业,您就无法为每个租户提供专用基础设施。
  • 忘记确定测试套件的范围。您的集成测试应该在租户上下文中运行。 如果您的测试在没有设置租户的情况下通过,那么他们正在测试您的生产用户不会访问的代码路径。
  • 不使用对抗性查询来测试 RLS 策略。编写测试,尝试在以租户 A 身份进行身份验证时访问租户 B 的数据。在 CI 中运行它们。 没有跨租户访问测试的通过测试套件会产生错误的信心。
  • 建筑租户管理是事后的想法。租户配置、配置和取消配置是一流的产品功能。 与产品一起构建管理工具,而不是在发布之后。
  • 跳过租户感知日志记录。每个日志行都应包含租户标识符。 当您在凌晨 2 点调试生产问题时,“出现问题”是没有用的。 “租户 acme_corp 在订单表上遇到了外键约束”是可行的。

简短版本

如果合规性推动您的架构,请选择每个租户数据库。 如果您需要中等规模的中等隔离,请选择模式分离。 如果您要优化速度和成本,请选择带有 RLS 的共享表。 第一天就构建租户解析中间件。 将缓存、日志、后台作业和测试的范围限定在租户上下文中。 并且不要为当前阶段过度设计隔离模型; 当您的客户需要并且您的收入支持时,您可以加强隔离。

常见问题

SaaS 的最佳多租户数据库架构是什么?

这取决于三个因素。 对于租户数量少于 100 个的受监管行业(金融科技、医疗保健)使用每个租户数据库。 对需要适度隔离的 50-5,000 个租户使用架构分离。 使用具有行级安全性的共享表来支持 5,000 多个租户或优化速度和成本的种子阶段产品。

什么是多租户 SaaS 中的行级安全性?

行级安全性 (RLS) 使用 PostgreSQL 策略将每个租户的查询限制为其自己的行。 每个表上的tenant_id 列标识所有权。 RLS 是最便宜的模型(一个数据库,每个租户基础设施成本为零),可处理 5,000 多个租户。 风险:绕过 RLS 的错误配置查询可能会跨租户泄漏数据。

与共享数据库相比,每个租户数据库的成本是多少?

每个租户数据库每月为每个租户增加 15-100 美元以上的计算、存储和备份费用。 对于 500 个租户,仅数据库成本就相当于每月 7,500 至 50,000 美元。 使用 RLS 的共享表运行一个数据库的费用总计为 15-200 美元/月。 模式分离介于一个数据库实例之间,但具有每个模式的迁移开销。

如何处理多租户应用程序中的租户解析?

使用子域解析、自定义域查找、路径前缀或 JWT 声明在请求管道边缘解析租户。 将解析的租户存储在请求范围的上下文中(Hono c.set()、Express req.tenant)。 下游代码不应重新解析租户。 该模式适用于所有三种隔离模型。

我可以为不同的客户层混合使用多租户隔离模型吗?

是的。 使用 RLS 为您的标准层运行共享表,并为需要合规性认证的企业客户运行每个租户数据库。 Stripe 和 Notion 使用了这种混合方法的变体。 它增加了操作复杂性,但可以让您以低成本为中小型企业提供服务,同时满足企业数据隔离要求。

相关阅读

联系我们

开始对话

告诉我们你的项目。我们将在 24 小时内回复,提供清晰的方案、预估时间线和价格区间。

电子邮件

hello@savibm.com

总部位于

阿联酋和印度