メインコンテンツまでスキップ

テナント別設定管理

このドキュメントの目的

マルチテナントSaaSにおけるテナントごとの設定管理を理解し、コード変更なしでテナントの挙動をカスタマイズする設計ができるようになることが目標です。

テナントオンボーディング設計ではテナントの作成・管理・削除のライフサイクルを学びました。本記事では、作成したテナントの設定・カスタマイズをどう管理するかに焦点を当てます。

具体例について: 本記事ではIDサービスを題材にした例(MFAポリシー、認証方式、セッション設定等)を使用していますが、設定駆動アーキテクチャの原則はSaaS全般に共通です。


設定駆動アーキテクチャ

基本的な考え方

マルチテナントSaaSでは、テナントごとに異なる要件(認証方式、UIテーマ、機能制限等)があります。これをテナントごとにコードを分岐させて実装すると、コードベースが急速に複雑化します。

アンチパターン: コードによる分岐

if (tenant == "company-a") {
// MFA必須
} else if (tenant == "company-b") {
// パスワードのみ
} else if (tenant == "company-c") {
// FIDO2
}
// テナントが増えるたびにコード変更...

設定駆動アーキテクチャでは、テナントの挙動をコードではなく設定データで制御します。

設定駆動アーキテクチャ:

┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Tenant A設定 │ │ Tenant B設定 │ │ Tenant C設定 │
│ │ │ │ │ │
│ mfa: required│ │ mfa: disabled│ │ mfa: optional│
│ session: 1h │ │ session: 8h │ │ session: 4h │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────────┼────────────────────┘


┌──────────────────┐
│ 共通エンジン │
│ │
│ 設定を読み取り │
│ 挙動を動的に変更 │
└──────────────────┘

メリット:

  • テナント追加時にコード変更が不要
  • テナント管理者が自分で設定を変更可能
  • テスト容易性(設定のバリエーションをテストすればよい)
  • デプロイなしで挙動を変更可能

設定のカテゴリ

テナント設定は大きく以下のカテゴリに分類されます。

1. 認証・セキュリティ設定

テナントの認証ポリシーやセキュリティ要件を制御します。

設定項目説明値の例
認証方式許可する認証方式password, fido2, sms_otp
MFAポリシーMFAの要求レベルrequired, optional, disabled
パスワードポリシーパスワードの複雑性要件最小長: 8, 大文字必須: true
セッション有効期限セッションタイムアウト3600秒, 28800秒
ロックアウトポリシー連続失敗時のアカウントロック試行回数: 5, ロック時間: 30分

2. UI・ブランディング設定

テナントごとのルック&フィールをカスタマイズします。

設定項目説明値の例
ロゴURLログイン画面のロゴhttps://example.com/logo.png
プライマリカラーテーマカラー#1a73e8
ログインページテキストカスタムメッセージ"Welcome to Company A Portal"
フッターリンク利用規約等のリンク[{label, url}, ...]

3. 機能・ポリシー設定

テナントが利用可能な機能やリソース制限を制御します。

設定項目説明値の例
最大ユーザー数テナント内のユーザー上限100, 1000, unlimited
許可されるGrant TypeOAuth 2.0のGrant Typeauthorization_code, client_credentials
API レート制限1秒あたりのリクエスト数100, 1000, 10000
ストレージ容量データ保存の上限1GB, 10GB, 100GB

4. 連携・統合設定

外部サービスとの連携設定です。

設定項目説明値の例
外部IdP連携フェデレーション設定Google, Azure AD, SAML
Webhook URLイベント通知先https://customer.com/webhook
メール送信設定カスタムSMTP設定smtp.customer.com

設定の階層化

オーバーライドモデル

すべてのテナントに個別の設定を1から定義するのは非効率です。階層化により、共通設定を共有しつつテナント固有の差分だけを管理します。

優先度(高い方が優先)

┌─────────────────────────────┐
│ レベル3: テナント個別設定 │ ← テナント管理者が設定
│ 例: session_timeout = 1800 │
├─────────────────────────────┤
│ レベル2: プラン別デフォルト │ ← プラン(Free/Standard/Enterprise)
│ 例: session_timeout = 3600 │
├─────────────────────────────┤
│ レベル1: システムデフォルト │ ← 全テナント共通のベースライン
│ 例: session_timeout = 7200 │
└─────────────────────────────┘

解決結果:
Tenant A (Enterprise, 個別設定あり) → session_timeout = 1800
Tenant B (Standard, 個別設定なし) → session_timeout = 3600
Tenant C (Free, 個別設定なし) → session_timeout = 7200

設定解決アルゴリズム

resolve(tenant_id, key):
1. テナント個別設定にkeyがあるか? → あればその値を返す
2. テナントのプラン別設定にkeyがあるか? → あればその値を返す
3. システムデフォルト設定のkeyの値を返す

階層化の利点

観点説明
運用効率大半のテナントはデフォルト設定で動作する
一貫性システムデフォルトを変更すると全テナントに反映
柔軟性特定テナントだけ異なる設定が可能
可視性テナントの設定がデフォルトからどう異なるか明確

設定のマージ戦略

設定がネスト構造(オブジェクト)の場合、マージ方法を明確にする必要があります。

浅いマージ(Shallow Merge):

システムデフォルト:
password_policy:
min_length: 8
require_uppercase: true
require_number: true

テナント個別設定:
password_policy:
min_length: 12

結果(浅いマージ):
password_policy:
min_length: 12
# require_uppercase, require_number は消える!

深いマージ(Deep Merge):

結果(深いマージ):
password_policy:
min_length: 12 ← テナント個別
require_uppercase: true ← デフォルトから継承
require_number: true ← デフォルトから継承

推奨: 一般的に深いマージの方が直感的で安全です。ただし、配列値やnull値の扱いについてルールを明確に定義する必要があります。


Feature Flags

テナント単位のFeature Flags

新機能をテナント単位でON/OFFする仕組みです。全テナント一斉リリースではなく、段階的にロールアウトできます。

Feature Flags テーブル:

┌─────────────────┬─────────────┬─────────┬─────────┬─────────┐
│ Feature │ Global │ Plan │ Tenant A│ Tenant B│
├─────────────────┼─────────────┼─────────┼─────────┼─────────┤
│ new_login_ui │ disabled │ - │ enabled │ - │
│ fido2_support │ disabled │ Ent:on │ - │ enabled │
│ batch_import │ enabled │ Free:off│ - │ - │
│ audit_export │ enabled │ - │ - │ - │
└─────────────────┴─────────────┴─────────┴─────────┴─────────┘

解決:
Tenant A (Standard): new_login_ui=on, fido2_support=off, batch_import=on
Tenant B (Enterprise): new_login_ui=off, fido2_support=on, batch_import=on

Feature Flagのライフサイクル

1. 導入            2. ロールアウト        3. 全体有効化       4. 削除
(disabled) (段階的に有効化) (全テナントON) (フラグ除去)

┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────┐
│ コード内に │ → │ 一部テナント │ → │ 全テナント │ → │ フラグ │
│ フラグ追加 │ │ で有効化 │ │ 有効化 │ │ コード削除│
└──────────┘ └──────────────┘ └──────────────┘ └──────────┘

重要: Feature Flagは一時的なものです。全テナントで有効化した後は、フラグのチェックコードを削除します。放置すると「Feature Flag地獄」になり、コードの可読性が著しく低下します。

段階的ロールアウト戦略

段階対象目的
Canary社内テナント(1テナント)初期バグの発見
Early Adopter協力的な顧客(5〜10テナント)実環境でのフィードバック
Gradual Rolloutプラン別に段階拡大リスクの段階的軽減
General Availability全テナント全体有効化

設定変更の安全性

バリデーション

設定変更時には、値の妥当性を検証します。

バリデーション層:

設定変更リクエスト


┌──────────────────┐
│ 1. 型チェック │ session_timeout: "abc" → エラー
├──────────────────┤
│ 2. 範囲チェック │ session_timeout: -1 → エラー
├──────────────────┤
│ 3. 整合性チェック │ mfa: required + 認証方式: password_only → エラー
├──────────────────┤
│ 4. 権限チェック │ Free planでEnterprise機能 → エラー
└──────────────────┘

▼ すべてパス
設定を適用

バリデーションルールの例

ルール種別エラーメッセージ
session_timeout は整数"session_timeout must be an integer"
範囲300 ≤ session_timeout ≤ 86400"session_timeout must be between 300 and 86400"
整合性MFA必須なら認証方式にMFA対応が含まれること"MFA-capable auth method required when MFA is mandatory"
権限Free planではカスタムドメイン不可"Custom domain is not available on Free plan"
依存関係FIDO2を有効にするにはRP ID設定が必要"rp_id is required when fido2 is enabled"

ドライラン

設定変更を実際に適用する前に、影響を確認できる仕組みです。

ドライラン リクエスト:
PUT /tenants/{id}/config?dry_run=true
{
"session_timeout": 300,
"mfa_policy": "required"
}

ドライラン レスポンス:
{
"valid": true,
"changes": [
{
"key": "session_timeout",
"current": 3600,
"proposed": 300,
"impact": "全アクティブセッションが5分後に期限切れ"
},
{
"key": "mfa_policy",
"current": "optional",
"proposed": "required",
"impact": "MFA未設定の42ユーザーが次回ログイン時にMFA登録必要"
}
]
}

ロールバック

設定変更が問題を引き起こした場合に、以前の設定に戻す仕組みです。

設定変更の履歴:

Version 1 (2024-01-01) ← 初期設定
Version 2 (2024-03-15) ← MFA有効化
Version 3 (2024-06-01) ← セッション短縮 ← 現在

│ 問題発生!

Version 2にロールバック

ロールバック設計のポイント:

  • 設定変更のたびにバージョンを記録する
  • 任意のバージョンにロールバック可能にする
  • ロールバック自体も新しいバージョンとして記録する(履歴の完全性)
  • ロールバックにもバリデーションを適用する(古い設定が現在の制約に適合するか)

設定のキャッシュ戦略

なぜキャッシュが必要か

テナント設定はリクエストごとに参照されます。毎回DBから読み取ると、パフォーマンスが著しく低下します。

キャッシュなし:
リクエスト → DB読み取り → 設定取得 → 処理
レイテンシー: +5ms〜20ms/リクエスト

キャッシュあり:
リクエスト → キャッシュ → 設定取得 → 処理
レイテンシー: +0.1ms/リクエスト

キャッシュ戦略の比較

戦略仕組みTTL例適用場面
TTLベース一定時間後に期限切れ5分即時反映が不要な設定
イベント駆動設定変更時にキャッシュ無効化-即時反映が必要な設定
起動時ロードアプリ起動時に全テナント設定をロード-テナント数が少ない場合

キャッシュ無効化

設定変更時の無効化フロー:

管理者が設定変更


┌──────────────┐
│ DB更新 │
└──────┬───────┘


┌──────────────┐
│ キャッシュ │
│ 無効化イベント│
└──────┬───────┘

┌────┼────┐
▼ ▼ ▼
App1 App2 App3 ← 全インスタンスのキャッシュを無効化

注意: マルチインスタンス構成では、1台のキャッシュだけでなく全インスタンスのキャッシュを無効化する必要があります。


設定駆動の限界

設定駆動アーキテクチャは強力ですが、すべてのテナント要件を設定だけで吸収できるわけではありません

設定駆動で対応できる              設定駆動では対応できない
───────────────────── ─────────────────────
パラメータの違い ビジネスロジック自体が異なる
セッション: 1h vs 8h テナントAは独自の承認フロー

ON/OFFの違い テナント固有の外部連携
MFA: 有効 vs 無効 テナントBは独自APIと連携

選択肢の違い データモデルの拡張
認証方式: password vs fido2 テナントCは独自の属性が必要

こうしたケースに対応するには、Plugin、Webhook、ルールエンジンなどの拡張メカニズムが必要です。

詳細: テナントカスタマイズパターン


まとめ

学んだこと

  • 設定駆動アーキテクチャにより、コード変更なしでテナントの挙動を変えられる
  • 設定は4カテゴリ(認証・UI・機能・連携)に分類される
  • 設定の階層化(システムデフォルト→プラン別→テナント個別)で効率的に管理する
  • Feature Flagsにより、テナント単位の段階的ロールアウトが可能
  • 設定変更にはバリデーション、ドライラン、ロールバックの安全策が必要
  • キャッシュ戦略により設定参照のパフォーマンスを確保する
  • 設定駆動で対応できない要件には拡張メカニズム(Plugin、Webhook等)が必要

次のステップ

  1. テナントカスタマイズパターン - 設定駆動の限界を超える拡張手法
  2. マルチテナントセキュリティ - セキュリティ上の考慮事項

最終更新: 2026-03-03 対象: SaaSアプリケーション開発者・アーキテクト