SaaS事業者にとってマルチテナントのデータ基盤設計は、避けて通れない課題です。テナントデータをどう分離するか、コストをどう配分するか、テナント数が増えた時にどうスケールするか――この3つの問いに答えられないまま進めると、数年後に大幅な作り直しを強いられます。本記事では、DB分離・スキーマ分離・行レベル分離の3パターンを比較し、実装例とベストプラクティスを解説します。

マルチテナントデータ基盤とは

マルチテナントデータ基盤とは、複数の顧客(テナント)のデータを1つの基盤で管理しつつ、テナント間の分離とガバナンスを担保する仕組みです。SaaS事業におけるデータ基盤はほぼ必ずマルチテナント要件が発生します。分析用途であれ、顧客に公開する機能であれ、テナント間の情報漏洩は即座に信頼失墜につながります。

設計の核は「分離方針の選択」と「スケーラビリティの見通し」です。初期に決めた方針は後から変更するコストが非常に高いので、事業成長のシナリオを描きながら慎重に選ぶ必要があります。SaaS企業のデータ基盤の事例も参考になります。

3つの分離パターン

マルチテナント分離のパターンは主に3種類です。DB分離(Database-per-Tenant)、スキーマ分離(Schema-per-Tenant)、行レベル分離(Shared Schema with Tenant ID)。それぞれ分離度・コスト効率・運用負荷が異なります。

【3パターンの構成】

パターン1: DB分離
  [Tenant A] --> [DB_A]
  [Tenant B] --> [DB_B]
  [Tenant C] --> [DB_C]
  ※ 完全分離、運用重め

パターン2: スキーマ分離
  [Tenant A] --> [DB_shared/schema_a]
  [Tenant B] --> [DB_shared/schema_b]
  [Tenant C] --> [DB_shared/schema_c]
  ※ バランス型

パターン3: 行レベル分離
  [Tenant A] --+
  [Tenant B] --+--> [DB_shared/table(tenant_id)]
  [Tenant C] --+
  ※ コスト最小、分離はRLSで担保
観点DB分離スキーマ分離行レベル分離
分離強度最強弱(RLS依存)
コスト効率
運用負荷高(DB数分の管理)
スケーラビリティ低(テナント数増で限界)高(理論上無制限)
横断分析困難容易
向いている規模少数・大口テナント数十〜数百数百〜無制限
セキュリティ要件最高レベル中〜高

小〜中規模SaaSでは行レベル分離がコスト効率で圧倒的に有利です。金融・医療など厳格な分離要件がある場合のみ、DB分離を検討しましょう。バランス型としてのスキーマ分離は、テナントごとにスキーマ進化を分けたい場合に有用です。

テナント分離の実装

行レベル分離の実装例を見てみましょう。Snowflakeの行アクセスポリシーを使い、テナントIDによる自動フィルタリングを実現できます。

-- Snowflake: テナントID単位の行アクセスポリシー
CREATE OR REPLACE ROW ACCESS POLICY tenant_rls
  AS (tenant_id STRING) RETURNS BOOLEAN ->
  CASE
    WHEN CURRENT_ROLE() = 'PLATFORM_ADMIN' THEN TRUE
    WHEN tenant_id = CURRENT_USER_TENANT() THEN TRUE
    ELSE FALSE
  END;

ALTER TABLE fact_events ADD ROW ACCESS POLICY tenant_rls ON (tenant_id);

スキーマ分離を採用する場合、Terraformなどのコード管理ツールでテナントごとのスキーマ作成を自動化するのが定石です。テナントオンボーディング時に自動的にスキーマ・初期テーブル・権限が作成される仕組みを整えておきましょう。

# Terraform: テナント別スキーマ自動作成
resource "snowflake_schema" "tenant_schema" {
  for_each = toset(var.tenant_ids)
  database = "ANALYTICS_DB"
  name     = "TENANT_${each.value}"
  comment  = "Schema for tenant ${each.value}"
}

resource "snowflake_schema_grant" "tenant_read" {
  for_each      = toset(var.tenant_ids)
  database_name = "ANALYTICS_DB"
  schema_name   = "TENANT_${each.value}"
  privilege     = "USAGE"
  roles         = ["TENANT_${each.value}_READER"]
}

いずれのパターンでも、クエリに必ずテナントIDフィルタが含まれる仕組みを担保することが絶対条件です。アプリケーションレイヤーで生SQLを書かせず、ORMやパラメータバインディングを使い、テナントIDの欠落を検知する仕組みを組みましょう。詳しくはセキュリティ設計も参照してください。

コスト配分の設計

マルチテナントでは「どのテナントがどれだけコストを使っているか」を把握することが、収益性の管理と料金プラン設計の両面で重要です。コスト配分方法は主に4つあります。

方式粒度実装難易度向いているケース
クエリタグベースSnowflake等、クエリタグ機能あり
ウェアハウス分離大口テナントのみ分離
ストレージ按分テナント別データ量で配分
アクセスログ按分BI利用量で配分

多くの場合、クエリタグベースのコンピュートコスト按分 + ストレージ按分の組み合わせが実用的です。料金プランの見直しに使うだけでなく、高コストテナントのクエリパターンを分析して最適化することもできます。Snowflakeのコスト最適化と併せて取り組むと効果的です。

スケーラビリティの考慮事項

テナント数が10から1,000に増えたとき、選んだ分離パターンがスケールするかを必ず検証してください。DB分離は数十を超えると運用が破綻しやすく、スキーマ分離も数百を超えるとメタデータ爆発を起こします。大規模SaaSでは行レベル分離+RLSの一択に近い状況です。

加えて、テナント間のデータ量の偏り(歪み)にも注意が必要です。特定の大口テナントが全データの80%を占めるような状況では、単純な行レベル分離ではそのテナントのクエリが遅くなります。この場合、大口テナントだけ別ウェアハウス/別スキーマに隔離するハイブリッド構成が効果的です。クラスタリングキーにテナントIDを含めるのも定石です。同時に、テナントごとのアクティビティログを別テーブルに分けるなど、物理的なパーティション分割も検討しましょう。将来の成長に備え、3倍、10倍のテナント数を想定した性能試験を初期段階から実施しておくことをおすすめします。

ベストプラクティス

実装時のベストプラクティスを4つ挙げます。第一に、テナントIDを必ず全テーブルに持たせる。暫定的でも例外的でも、テナントIDのないテーブルは作らない。第二に、テナントIDでのクラスタリングキー設定。大規模テーブルで劇的にクエリ効率が向上します。第三に、定期的なセキュリティ監査。RLSポリシーが期待通り動作しているかを、四半期ごとに本番環境で確認します。第四に、オンボーディング・オフボーディングの自動化。テナントの追加と削除、データエクスポート、完全削除までをTerraformなどのコードで管理し、属人化を防ぎます。テナント削除時のデータ残存は法的リスクにもなるため、削除プロセスを明文化することが重要です。

まとめ

マルチテナントデータ基盤の設計は、DB分離・スキーマ分離・行レベル分離の3パターンから、事業規模とセキュリティ要件に応じて選択するのが基本です。多くのSaaSでは行レベル分離がコスト効率とスケーラビリティの面で最適解となります。テナント分離・コスト配分・スケーラビリティの3点を初期設計に織り込み、事業成長に耐える基盤を構築しましょう。権限管理Snowflake入門も合わせて参考にしてください。

よくある質問(FAQ)

Q. マルチテナントの分離パターンはどれを選ぶべきですか?

小〜中規模なら行レベル分離がコスト効率最高、セキュリティ要件が厳しいならDB分離、バランス型はスキーマ分離です。事業成長のシナリオを描きながら、将来のテナント数を想定して選択しましょう。

Q. テナント間のデータ漏洩を防ぐには?

RLS(行レベルセキュリティ)の設定、テナントID必須のクエリポリシー、定期的なセキュリティ監査の3つが基本対策です。さらに、アプリケーションレイヤーで生SQLを禁止し、ORMによるパラメータバインディングを徹底することも有効です。

Q. テナント別のコストをどう計算しますか?

クエリログのテナントIDタグでコンピュート使用量を按分する方法と、ストレージのテナント別集計を組み合わせるのが一般的です。Snowflakeのクエリタグ機能やBigQueryのラベルを活用することで、高精度な配分が可能です。