コラム / アーキテクチャ・設計 /
マルチテナント SaaS のデータ分離設計 ─ Row-Level Security とテナント別DBの分岐点
テナント数100で破綻する設計と、テナント数10,000まで耐える設計の境目を実数値で
📖 約11分 / 公開日: 2026/05/26
マルチテナント SaaS の設計レビューを依頼される時、9割は同じ質問から始まります。「tenant_id カラムで分離しているが、これで本当に安全なのか」。
結論から書きます。テナント数が3桁前半、扱うデータがビジネスデータ(PII軽め)であれば、tenant_id カラム + アプリケーション層フィルタで実害は出ません。ただし、tenant_id の WHERE 漏れが1箇所でも発生した瞬間に他テナントのデータが見えるため、レビュー体制とテストの厚みで守る設計です。これを「設計の問題」と捉えるか「運用で守る」と捉えるかで、選択肢が変わります。
テナント数が500を超え始めると、tenant_id 方式は別の問題に直面します。Postgres のクエリプランナがテナントごとの統計分布を持てないため、同じクエリでも特定の大規模テナントだけ全件スキャンになる現象が起きる。直近の事例では、月間アクティブ800テナント規模の B2B SaaS で、上位5テナントが全データの78%を占めており、その5テナント向けクエリだけ p99 が18秒に膨らんでいました。pg_partman でテナント単位パーティションに切り直したところ p99 が420msまで落ちましたが、ここまで来ると「最初からスキーマ分離にしておけば」という後悔が現場で出ます。
スキーマ分離(PostgreSQL の Schema 単位でテナントを区切る方式)の話に移ります。Salesforce が長らく採用していた方式として有名ですが、現代の Postgres ではテナント数が1,000を超えると pg_dump/pg_restore とマイグレーション運用が地獄になります。Rails の Apartment gem が事実上メンテナンス停止したのも、この運用コストが理由として大きい。100テナント以下なら有力候補、500を超えるなら他の選択肢を検討すべき、というのが実務の肌感です。
ここで PostgreSQL の Row-Level Security(RLS) が選択肢として浮上します。RLS はテーブル単位で「現在のセッションロールに応じて見える行を絞る」機能で、Supabase の認証連携が話題になって以降、業務 SaaS でも採用例が増えました。アプリ層で tenant_id の WHERE を書き忘れても、DBレイヤーで強制的にフィルタが効くため、漏えい事故の最後の砦として機能します。
ただし、RLS には3つの罠があります。1つ目はコネクションプーリングとの相性。pgbouncer の transaction pooling モードでは SET ROLE が再利用される接続で意図せず引き継がれるため、テナント切り替え時に必ず RESET ROLE するか、SET LOCAL を使ってトランザクション内に閉じる必要がある。これを知らずに本番投入し、稀に他テナントの行が見える、という事故が直近で2件ありました。
2つ目はパフォーマンス劣化。RLS のポリシー関数が複雑だと、JOIN を含む大規模クエリでプランナが最適化を諦め、シーケンシャルスキャンに落ちることがある。EXPLAIN ANALYZE で見ると Filter 句で大量の行を捨てているケースが典型で、ポリシー関数を STABLE 指定し、tenant_id を SECURITY DEFINER 関数経由で取得する形に書き直すと改善します。これは Postgres の公式ドキュメントにも明記されているチューニングポイントですが、後から気づくと書き換え範囲が広い。
3つ目はマイグレーションでのポリシー再付与漏れ。ALTER TABLE で大きな変更を入れた際に RLS ポリシーが意図せず無効化される、あるいは新しいカラム追加時にポリシーが追従していない、という事故が起きます。CI で「RLS が enabled になっていないテーブルを検出する」テストを必ず書く必要があります。Supabase が提供している rls-doctor のようなツールを参考にカスタム実装するか、テスト用のロールで実際に他テナントの行が見えないことを確認する E2E テストを入れる。後者の方が確実です。
テナント別DB分離の判断ラインに移ります。テナント別DB(database-per-tenant)は、コンプライアンス要件で「他社のデータと物理的に分離してほしい」と契約に書かれているケース、あるいはエンタープライズ向けで1テナントあたり数十万円以上の月額を取れるケースで意味を持ちます。AWS RDS のインスタンス料金は最小構成でも月15ドル、Aurora Serverless v2 で月10ドル台から組めるとはいえ、運用工数を含めると1テナントあたり月50ドルは現実的な下限です。テナントあたりの ARPU が月500ドルを下回るなら、データ分離以外の価値で正当化できない限り採用すべきではない。
中間案として、PlanetScale や Neon が提供する「ブランチ機能」を使った論理分離があります。Neon は1プロジェクトに対して複数のブランチを作成でき、ストレージは共有しつつ独立した接続を提供します。テナントごとにブランチを切る運用は2024年から事例が出始めており、テナント数200程度の B2B SaaS で月額1,200ドル前後で運用している例を確認しています。完全な物理分離ではないが、論理的には独立しており、特定テナントだけ過去時点へロールバックする要件にも対応できる。コンプライアンス審査では「物理分離」と言い切れないため、業界によっては受け入れられない点に注意が必要です。
MongoDB や DynamoDB を使う場合の話も少し。MongoDB はコレクション単位の分離が容易ですが、Atlas の料金体系がコレクション数に比例しないため、テナント数1,000を超えても1クラスタで運用可能です。ただしインデックス設計とシャーディングキーを誤ると単一テナントが全リソースを食う現象が起きます。DynamoDB はパーティションキーに tenant_id を含める設計が定番ですが、Hot Partition 問題でテナントごとのスループット偏りに脆弱です。GSI(Global Secondary Index) の設計を含めて、最初から正しく組まないと後で書き直す範囲が広い。
推奨する設計フローを書いておきます。テナント数の見込み(1年後・3年後)、テナントあたり ARPU、コンプライアンス要件、データの種類(PII / 決済 / 医療等) の4軸で分岐します。ARPU 月10万円以上 かつ コンプライアンス要件あり なら、テナント別DB。テナント数 1,000以上見込み なら、tenant_id + RLS + 大規模テナントだけ別 DB に切り出すハイブリッド。それ以外は tenant_id + RLS + 厳格なテスト体制が、コストと運用負荷のバランスとして現実解です。
ここに書いたことは、すべて直近1年で実際に相談を受け、設計レビューや書き直しを行った案件から抽出した知見です。多くの SaaS が「最初の設計で勝負が決まる」領域で、テナント数200を超えてから書き直すコストは、新規構築の3〜5倍に膨らみます。設計段階で迷ったら、テナント数の見込みを保守的に置きすぎないことです。当初想定の3倍を上限値として設計しておくと、書き直しを避けられる確率が上がります。
結論から書きます。テナント数が3桁前半、扱うデータがビジネスデータ(PII軽め)であれば、tenant_id カラム + アプリケーション層フィルタで実害は出ません。ただし、tenant_id の WHERE 漏れが1箇所でも発生した瞬間に他テナントのデータが見えるため、レビュー体制とテストの厚みで守る設計です。これを「設計の問題」と捉えるか「運用で守る」と捉えるかで、選択肢が変わります。
テナント数が500を超え始めると、tenant_id 方式は別の問題に直面します。Postgres のクエリプランナがテナントごとの統計分布を持てないため、同じクエリでも特定の大規模テナントだけ全件スキャンになる現象が起きる。直近の事例では、月間アクティブ800テナント規模の B2B SaaS で、上位5テナントが全データの78%を占めており、その5テナント向けクエリだけ p99 が18秒に膨らんでいました。pg_partman でテナント単位パーティションに切り直したところ p99 が420msまで落ちましたが、ここまで来ると「最初からスキーマ分離にしておけば」という後悔が現場で出ます。
スキーマ分離(PostgreSQL の Schema 単位でテナントを区切る方式)の話に移ります。Salesforce が長らく採用していた方式として有名ですが、現代の Postgres ではテナント数が1,000を超えると pg_dump/pg_restore とマイグレーション運用が地獄になります。Rails の Apartment gem が事実上メンテナンス停止したのも、この運用コストが理由として大きい。100テナント以下なら有力候補、500を超えるなら他の選択肢を検討すべき、というのが実務の肌感です。
ここで PostgreSQL の Row-Level Security(RLS) が選択肢として浮上します。RLS はテーブル単位で「現在のセッションロールに応じて見える行を絞る」機能で、Supabase の認証連携が話題になって以降、業務 SaaS でも採用例が増えました。アプリ層で tenant_id の WHERE を書き忘れても、DBレイヤーで強制的にフィルタが効くため、漏えい事故の最後の砦として機能します。
ただし、RLS には3つの罠があります。1つ目はコネクションプーリングとの相性。pgbouncer の transaction pooling モードでは SET ROLE が再利用される接続で意図せず引き継がれるため、テナント切り替え時に必ず RESET ROLE するか、SET LOCAL を使ってトランザクション内に閉じる必要がある。これを知らずに本番投入し、稀に他テナントの行が見える、という事故が直近で2件ありました。
2つ目はパフォーマンス劣化。RLS のポリシー関数が複雑だと、JOIN を含む大規模クエリでプランナが最適化を諦め、シーケンシャルスキャンに落ちることがある。EXPLAIN ANALYZE で見ると Filter 句で大量の行を捨てているケースが典型で、ポリシー関数を STABLE 指定し、tenant_id を SECURITY DEFINER 関数経由で取得する形に書き直すと改善します。これは Postgres の公式ドキュメントにも明記されているチューニングポイントですが、後から気づくと書き換え範囲が広い。
3つ目はマイグレーションでのポリシー再付与漏れ。ALTER TABLE で大きな変更を入れた際に RLS ポリシーが意図せず無効化される、あるいは新しいカラム追加時にポリシーが追従していない、という事故が起きます。CI で「RLS が enabled になっていないテーブルを検出する」テストを必ず書く必要があります。Supabase が提供している rls-doctor のようなツールを参考にカスタム実装するか、テスト用のロールで実際に他テナントの行が見えないことを確認する E2E テストを入れる。後者の方が確実です。
テナント別DB分離の判断ラインに移ります。テナント別DB(database-per-tenant)は、コンプライアンス要件で「他社のデータと物理的に分離してほしい」と契約に書かれているケース、あるいはエンタープライズ向けで1テナントあたり数十万円以上の月額を取れるケースで意味を持ちます。AWS RDS のインスタンス料金は最小構成でも月15ドル、Aurora Serverless v2 で月10ドル台から組めるとはいえ、運用工数を含めると1テナントあたり月50ドルは現実的な下限です。テナントあたりの ARPU が月500ドルを下回るなら、データ分離以外の価値で正当化できない限り採用すべきではない。
中間案として、PlanetScale や Neon が提供する「ブランチ機能」を使った論理分離があります。Neon は1プロジェクトに対して複数のブランチを作成でき、ストレージは共有しつつ独立した接続を提供します。テナントごとにブランチを切る運用は2024年から事例が出始めており、テナント数200程度の B2B SaaS で月額1,200ドル前後で運用している例を確認しています。完全な物理分離ではないが、論理的には独立しており、特定テナントだけ過去時点へロールバックする要件にも対応できる。コンプライアンス審査では「物理分離」と言い切れないため、業界によっては受け入れられない点に注意が必要です。
MongoDB や DynamoDB を使う場合の話も少し。MongoDB はコレクション単位の分離が容易ですが、Atlas の料金体系がコレクション数に比例しないため、テナント数1,000を超えても1クラスタで運用可能です。ただしインデックス設計とシャーディングキーを誤ると単一テナントが全リソースを食う現象が起きます。DynamoDB はパーティションキーに tenant_id を含める設計が定番ですが、Hot Partition 問題でテナントごとのスループット偏りに脆弱です。GSI(Global Secondary Index) の設計を含めて、最初から正しく組まないと後で書き直す範囲が広い。
推奨する設計フローを書いておきます。テナント数の見込み(1年後・3年後)、テナントあたり ARPU、コンプライアンス要件、データの種類(PII / 決済 / 医療等) の4軸で分岐します。ARPU 月10万円以上 かつ コンプライアンス要件あり なら、テナント別DB。テナント数 1,000以上見込み なら、tenant_id + RLS + 大規模テナントだけ別 DB に切り出すハイブリッド。それ以外は tenant_id + RLS + 厳格なテスト体制が、コストと運用負荷のバランスとして現実解です。
ここに書いたことは、すべて直近1年で実際に相談を受け、設計レビューや書き直しを行った案件から抽出した知見です。多くの SaaS が「最初の設計で勝負が決まる」領域で、テナント数200を超えてから書き直すコストは、新規構築の3〜5倍に膨らみます。設計段階で迷ったら、テナント数の見込みを保守的に置きすぎないことです。当初想定の3倍を上限値として設計しておくと、書き直しを避けられる確率が上がります。