マルチテナントSaaSアーキテクチャ:3つのパターンと選び方

マルチテナントSaaSアーキテクチャ:3つのパターンと選び方

行レベル(Row-level)、テナントごとのスキーマ(schema-per-tenant)、それともテナントごとのデータベース(database-per-tenant)?初期段階での選択が、どれだけスムーズにスケールできるかを決定します。

マルチテナンシーは、SaaSが成長し始めるまでは抽象的に感じるアーキテクチャ上の決定の1つです。しかし成長し始めると、データのクエリ方法、データベースの拡張、マイグレーションの実行、さらには顧客の隔離についての考え方に至るまで、あらゆることに影響を与えます。初期段階で正しい選択をすれば、数千ものアカウントへとスムーズにスケールできます。間違えれば、顧客が見守る中で成長の真っ只中にデータ層を書き直すことになります。

本日2026年7月9日現在、マルチテナントSaaSは例外ではなく標準となっています。チーム向けのツールを構築している場合でも、エンタープライズ向けに販売している場合でも、従量課金制を提供している場合でも、この決定を下すことになります。朗報なのは、ほとんどのプロダクトにとって適切な答えはインターネットで言われているよりもシンプルだということです。

3つの標準的なパターン

SaaSバックエンドでテナントを隔離するための十分に確立された方法は3つあり、これらは隔離性と運用コストのトレードオフの関係にあります。

行レベルのテナンシー(共有スキーマ)

すべてのテーブルに tenant_id カラムが存在します。すべてのクエリがこれでフィルタリングされます。1つのデータベース、1つのスキーマに全テナントが同居します。これは最も理解しやすく、最も運用コストが低いパターンです。

プロジェクト管理ツールを構築していると想像してみてください。 tasks テーブルは別々のストレージに分割されず、代わりにすべての行にそれを所有するテナントのIDが含まれます。ユーザーがタスクを照会するとき、アプリケーションはWHERE句を追加します: WHERE tenant_id = current_user.tenant_id.

テナントごとのスキーマ(Schema-Per-Tenant)

各テナントは共有データベース内に独自のPostgreSQLスキーマを持ちます。各スキーマが独自のネームスペースとなるため隔離性はより強くなりますが、管理するオブジェクトが増えます。マイグレーションはより複雑になり、複数のスキーマにわたって実行することになります。このパターンは、行レベルと完全な隔離の中間に位置します。

テナントごとのデータベース(Database-Per-Tenant)

各テナントに専用のデータベースまたはインスタンスが割り当てられます。隔離性は最大となり、1つのテナントのデータは完全に分離されたストレージに存在します。運用上の負荷も最大になり、顧客ごとに個別のデータベースインスタンス、バックアップ、アップグレードを管理することになります。

なぜほとんどのSaaSで行レベルが勝るのか

圧倒的多数のB2B SaaSプロダクトにおいて、行レベルのマルチテナンシーが適切なデフォルトです。最も運用コストが安く、マイグレーションの実行が最も容易で、創業者たちの予想以上にスケールします。

異論は常に「しかし隔離性はどうなるのか?」というものです。そして、ここにPostgresの強力な回答があります。

行レベルセキュリティ(RLS)とPostgres

Postgresは行レベルセキュリティ(Row-Level Security / RLS)と呼ばれる機能を提供しています。RLSを使用すると、データベース自体が、クエリが自テナントの行のみを参照できるように強制できます。データベース内で一度ポリシーを設定すれば、バグのあるクエリであってもテナント間でデータが漏洩することはありません。

マネージドPostgresプラットフォームであるSupabaseは、RLSをネイティブなモデルとして採用しています。ポリシーを定義することで、アプリケーション層だけでなく、データベース自体がセキュリティ境界となります。

これと tenant_id をすべてのテーブルに配置し、それを先頭にしたインデックスと組み合わせることで、このパターンは大規模な顧客ベースにも快適に対応します。データベースが強制適用を行うため、アプリケーション側でフィルタリングを忘れずに記述する必要はありません。

RLSに関する実用上の注意点

経験からの重要な詳細の1つ:ヘルパー関数が行ごとではなくクエリごとに1回実行されるようにRLSポリシーを記述してください。行ごとにルックアップを再評価するポリシーは、テーブルが大きくなるにつれて高速だったエンドポイントを密かに低速化させます。解決策は、クエリプランナーがそれを行ごとではなく開始時の1回限りのチェックであるinit-planとして実行するようにチェックをラッピングすることです。

より強力な隔離段階へ移行すべきタイミング

行レベルはほとんどの場合機能します。しかし、一部の顧客はそれ以上のものを必要とします。

反射的ではなく、計画的に移行しましょう:

  • 規制上または契約上の隔離 — 顧客が物理的に分離されたデータベースに自社のデータを保管することを要求している場合。規制の厳しい業界にいるか、それを義務付ける契約条項があるのかもしれません。
  • ノイジーネイバー(近隣騒音)リスク — 1社の大手顧客(ホエール顧客)のワークロードが他のすべての顧客のパフォーマンスを低下させる場合。分離されたインフラストラクチャがこれを解決します。
  • テナントごとのカスタマイズ — データだけでなく、スキーマ自体が真に分岐している場合。顧客ごとに根本的に異なる構造を保存しているケースです。

その場合でも、ハイブリッドアプローチが効果的です。ほとんどのテナントは行レベルに維持し、最大規模または最も機密性の高いアカウントのみを専用データベースに昇格させます。

重要な設計原則

何を選択するにしても、マルチテナンシーは初期段階で組み込んでください。後から付け足してはいけません。

重要なすべての場所に tenant_id を配置する

追加する: tenant_id をすべてのドメインテーブルに追加し、複合インデックスの先頭に配置します。これによりクエリが高速化し、データが自然にテナントごとに整理された状態に保たれます。

テナントの識別においてクライアントを絶対に信用しない

リクエストパラメータやクッキーからではなく、常に認証済みセッションからテナントを導出してください。クライアントに「あなたはどのテナントですか?」と尋ねると、悪意のある、あるいはバグのあるクライアントが偽る可能性があります。

データベース層で隔離を強制する

アプリケーションがWHERE句を忘れずにつけることだけに頼らないでください。データベースの制約とRLSを使用して、データ漏洩を不可能にしてください。開発者がどこかでフィルターを忘れたとしても、データベース自体がその間違いを防ぎます。

テナントのプロビジョニングをテスト済みの1つのコードパスにする

新しいテナントを追加するときは、テストされた1つの明確なプロセスを実行してください。コードベースの異なる部分で異なる方法でテナントを作成させないでください。一貫性がバグを防ぎます。

最も痛手となる間違い

間違いとは「間違った」モデルを選ぶことではありません。テナンシーを暗黙のままにし、隔離ロジックをコードベース全体に散在させることです。結果として、あるエンドポイントにはWHERE句があり、別のエンドポイントにはSQL JOINがあり、明確なルールがない状態に陥ります。

マルチテナンシーを集約しましょう。データベースでそれを強制します。一度ポリシーを設定すれば、そこから構築を進めることができます。これにより進化する自由を維持できます。

結論

マルチテナントアーキテクチャは基礎となる選択です。Postgresの行レベルセキュリティを備えた行レベルテナンシーは、ほとんどのSaaSにとって適切なデフォルトです。低コストでスケールし、データベースが隔離を強制します。規制、パフォーマンスの隔離、あるいは真のスキーマの分岐など、明確な理由がある場合にのみ、より強力なパターンへと移行してください。初日から組み込み、選択肢を文書化することで、スムーズにスケールできるようになります。

メリット

  • 行レベルテナンシーは、ほとんどのプロダクトにとって最も運用コストが安くシンプルです。
  • Postgresの行レベルセキュリティは隔離ロジックをデータベース内に移動させ、そこで透過的に強制適用されます。
  • 単一のデータベース、1つのスキーマにより、マイグレーションやバックアップがシンプルになります。
  • 再アーキテクチャ化することなく、後から個々のテナントをより強力な隔離環境へアップグレードできます。
  • この tenant_id + インデックス先頭配置のパターンは、大規模な顧客ベースにも拡張可能です。

デメリット

  • 行レベルの隔離は、物理的なデータ分離を求める規制上または契約上の要件には十分ではありません。
  • 単一のノイジーテナントの重いクエリが、同一データベース上の他のテナントに影響を与える可能性があります。
  • RLSポリシーのミス(行ごとのロジック再評価など)により、パフォーマンスが密かに激減する可能性があります。
  • 後から行レベルからテナントごとのスキーマまたはテナントごとのデータベースへ移行することは、複雑でリスクを伴います。
  • 開発者は常にテナントフィルターを含めるよう自律する必要があります。データベースが支援してくれますが、アプリケーションのバグが発生する可能性は依然としてあります。

注意事項

この記事は教育目的であり、一般的なベストプラクティスに基づいています。元資料はブログ記事からのものです。アーキテクチャ上の決定を下す前に、主張を元の出版物およびご自身の要件と照らし合わせて検証する必要があります。規制およびコンプライアンス要件は業界や管轄区域によって異なります。特定のユースケースについては法務およびセキュリティの専門家にご相談ください。ご自身の環境でRLSポリシーを徹底的にテストし、特に大規模環境でのパフォーマンスの挙動を確認してください。この記事は、ミッションクリティカルなシステムに対するプロのアーキテクチャレビューに代わるものではありません。

よくある質問

  • SaaSにおけるマルチテナンシーとは何であり、なぜ重要なのですか?
  • Postgresの行レベルセキュリティは、どのようにしてテナント間のデータ漏洩を防ぐのですか?
  • 行レベルテナンシーの代わりにテナントごとのスキーマ(schema-per-tenant)を使用すべきなのはどのようなときですか?
  • ノイジーネイバー(Noisy-neighbor)問題とは何であり、SaaSアーキテクチャにどのように影響しますか?
  • 既存のシングルテナントデータベースに tenant_id を追加するにはどうすればよいですか?
  • 行レベルから始めて、後からテナントごとのデータベース(database-per-tenant)にアップグレードすることはできますか?
  • 大規模環境におけるRLSポリシーのパフォーマンスへの影響は何ですか?
  • アプリケーションでマルチテナントの隔離性をテストするにはどうすればよいですか?

タグ

#saas #architecture #postgres #scaling #multitenant #database #security #rls

Free field guide

API Security Testing Checklist

A practical workflow for testing authentication, authorization, input handling, business logic, and evidence without losing track of scope.