凌晨2点,Festease的告警响了。租户A的用户数据赫然出现在租户B的仪表盘上——这种事故,作者在过去三年里踩了7次。他最终用NestJS+MongoDB/PostgreSQL的混搭方案,把多租户架构从"能用"推到"敢用"。
多租户的真相:不是技术问题,是成本问题
SaaS的默认答案早就写死了。Slack、Shopify、Notion,你天天用的工具全是多租户——一套代码、一个部署、逻辑隔离。替代方案是给每个客户单开实例,15个客户还行,150个的时候 migration 能跑死你。
每个租户要自己的数据、配置、甚至品牌皮肤。但没人愿意一开始就为专属基础设施买单。问题的本质变成:怎么让客户感觉拥有独立系统,实际上又不真的独立运行?
作者的经验很直白:多租户最难的不是让它跑起来,是让它安全地、大规模地跑起来,同时别把自己淹死在运维里。
三种模式业内翻来覆去用。共享数据库+共享表最简单,加一列 tenantId 了事。便宜、迁移快,直到有人漏写 WHERE tenantId = ?——数据泄露就这么来的。Festease早期用的就是这个,作者承认"对于快速迭代的产品,这是正确选择"。
共享数据库+独立Schema是折中。PostgreSQL 的 tenant_abc.orders、tenant_xyz.orders 让数据库引擎帮你守边界。代价是每次 migration 要跑N遍,中途失败就是 schema 漂移。工具能救一点,但工作量实打实增加。
一租户一数据库是隔离天花板。连接池管理、备份膨胀、监控噪音,问题换了一批但同样头疼。适合大客户、高合规场景。
生产环境教会的:别选一种,用 tiers 分层
作者的实战建议反直觉:三种模式都用。把租户按需求分层,而非一刀切。
起步客户扔共享Schema,构建快、运维便宜。当客户要求专属资源,或流量开始影响邻居,再往上迁移。这种"升级路径"的设计,比一开始就选最复杂的方案更经得起折腾。
技术实现上,NestJS 的依赖注入帮了大忙。作者用自定义装饰器 + 拦截器,把租户上下文注入请求链路。数据库连接根据租户配置动态切换——共享表的走默认池,独立Schema的切到对应 search_path,独立数据库的则拉起专用连接。
关键细节在边界检查。每个数据访问层强制校验租户上下文,查询构造器自动追加租户过滤。作者说这套机制"不是防恶意,是防手滑"——凌晨2点的bug往往来自漏写一行代码,而非复杂攻击。
监控也分层。共享Schema的租户看聚合指标,独立资源的看专属面板。告警阈值按层级设置,避免小客户的抖动淹没大客户的真实故障。
那些"设计文档里很美"的坑
作者列了几条血泪教训。连接池在混合模式下是噩梦——共享租户吃光连接,独立数据库的租户反而连不上。最终方案是给不同层级配独立池,用中间件做流量整形。
Migration 工具选了 TypeORM 的 migration:run,但多Schema场景下要包装一层租户遍历脚本。作者强调"失败回滚必须原子",否则就会出现 tenant_001 改了表、tenant_002 没改的诡异状态。
缓存是另一个雷区。Redis key 必须带租户前缀,作者见过缓存穿透导致跨租户数据泄露的案例。最终方案是 key 结构强制规范 + 定期扫描异常 key 的巡检脚本。
日志和审计按租户分离存储。共享Schema的客户看脱敏后的聚合日志,独立资源的可以下原始日志。合规审计时,这套分层设计省了大量数据筛选的工夫。
作者最后提了一个还在验证的假设:随着 Serverless 数据库(如 Neon、Supabase)成熟,"一租户一数据库"的成本曲线可能会快速下降。到时候现在的 tiers 分层策略,或许会被更激进的隔离方案取代。
你的多租户架构现在卡在哪个层级?是还在共享表里埋 tenantId,还是已经被 migration 的N次执行磨没了耐心?
热门跟贴