Technical document

シングルテナントからホワイトレーベルまで、アカウント管理を3階層で設計する

アカウント管理を考えるとき、ログイン画面を一つ作れば終わるとは限りません。利用者、所属するテナント、提供するブランド、管理者の範囲、データの正本、請求や監査の単位をどこまで分けるかで、必要な状態と検証が増えます。シングルテナントで数時間で形になる仕組みが、マルチテナントでは数日、ホワイトレーベルではさらに長期化するのは、機能の数よりも境界の組み合わせが増えるためです。本稿では、3階層を別の製品名ではなく、責務とリスクの段階として整理します。
アカウント設計 / マルチテナント約17分公開日 2026年9月12日更新日 2026年9月12日
シングルテナント、マルチテナント、ホワイトレーベルへ段階的に広がるアカウント管理のイラスト

Summary

この文書の要点

  • シングルテナント、マルチテナント、ホワイトレーベルは、ログイン方式の違いではなく、データ・権限・運用・ブランドの所有境界が増える段階です。
  • 複雑さは機能数に比例するだけではなく、利用者、テナント、ブランド、ロール、機能、環境を組み合わせた検証点として増えます。『指数関数的』という表現は、この組み合わせが急速に膨らむ実務上のリスクを示します。
  • アカウント、テナント、メンバーシップ、ロール、ブランドを別のエンティティとして持ち、リクエストごとに検証済みのテナント文脈を組み立てます。
  • 将来の販売形態を先回りして全部実装せず、次の階層へ進む条件と、進まない場合の可逆性を先に決めることが重要です。

アカウント管理の難しさは、ログイン後に始まります

メールアドレスとパスワードを確認し、利用者を画面へ入れるだけなら、最小の認証機能で実現できます。しかし業務アプリでは、誰がどの組織に所属し、どのデータを読み書きでき、どの管理操作を実行できるかを毎回判断します。退職、異動、招待の期限切れ、複数組織への兼務、緊急停止、監査ログまで含めると、アカウントは単なるログイン情報ではなく、変化する関係の集合になります。

さらに提供先が増えると、同じ利用者がどのテナントにいるか、テナントごとに契約機能や保持期間が違うか、運営者が顧客の管理者を代理できるかを分ける必要があります。ブランド名や独自ドメインを切り替えるホワイトレーベルでは、見た目の変更だけでなく、誰が招待を発行し、どのサポート担当が監査ログを見られるかまで変わります。

この文書でいう3階層は、技術の優劣を示すものではありません。必要な分離の数を見積もるための分類です。階層を一つ上げるたびに、データモデル、認可、画面、通知、請求、監視、バックアップ、サポートの境界を再検証する必要があります。

  • 認証: 利用者本人であることを確認する。
  • 所属: 利用者がどのテナントへ参加しているかを確認する。
  • 認可: そのテナントで何をしてよいかを確認する。
  • 運用: 招待、停止、監査、削除、復旧を誰が担当するかを確認する。

3階層は、所有境界が増える順に理解します

シングルテナントは、一つの顧客または一つの利用組織が、専用の実行環境やデータ領域を使う構成です。利用者と組織の関係がほぼ一対一なので、最初はアカウント、管理者、一般利用者という少数のロールで始められます。環境ごとに設定を分ければ、データ漏えいの経路も比較的限定できます。ただし、専用構成でも招待、停止、監査、バックアップを省略してよいわけではありません。

マルチテナントは、複数の顧客組織が共有するアプリケーションやデータベースを使いながら、互いのデータと権限を分離する構成です。すべての業務データにtenant_idを持たせるだけでは不十分で、API、SQL、キャッシュ、検索インデックス、キュー、ファイルパス、ログ、管理画面の各経路でテナント境界を守ります。テナントをまたいだ集計や運営者によるサポートアクセスも、通常の利用者とは別の権限として設計します。

ホワイトレーベルは、複数のブランドや販売主体が、共通の機能基盤を自分たちのサービスとして提供する段階です。ブランド設定、独自ドメイン、メール送信元、利用規約、問い合わせ窓口、契約機能、請求単位、サポート権限が加わります。ブランドの管理者が自分の配下テナントを作れる構成では、運営者、ブランド管理者、顧客管理者、利用者という階層を表現しなければなりません。

  • シングルテナント: 分離は環境単位で担保しやすいが、顧客ごとの運用が増える。
  • マルチテナント: 共有コストを下げられるが、リクエストごとのテナント分離を強制する。
  • ホワイトレーベル: テナントの上にブランドと委任管理を追加し、表示・契約・サポートも分離する。
3階層の責務境界(一般化した整理)
Single tenant
  account -> one organization -> isolated data / roles

Multi-tenant
  account -> memberships -> tenant -> shared runtime with isolation
  platform operator may see cross-tenant operations by explicit policy

White-label
  platform operator -> brand -> tenant -> memberships -> account
  brand controls presentation, policy defaults, domain and support boundary

『指数関数的に増える』とは、組み合わせと検証点が膨らむことです

シングルテナントの試作では、利用者が自分の組織のデータを見られるか、管理者だけが招待できるかを確認すれば、主要な受け入れ条件を絞れます。マルチテナントにすると、利用者Aがテナント1のデータを見られ、同じ利用者が所属するテナント2のデータは見られない、という組み合わせが生まれます。キャッシュや非同期ジョブが加わると、正しいtenant_idが遅延処理や再試行でも維持されるかも検証します。

ホワイトレーベルでは、ブランド管理者が自分の配下だけを管理できるか、運営者のサポート操作が顧客へ誤って見えないか、ブランドごとのメールやドメインが混ざらないかを確認します。利用者、テナント、ブランド、ロール、機能、環境という軸を増やすたび、許可と拒否の組み合わせが積み上がります。数学的な指数関数を厳密に予測するというより、機能数だけで工数を見積もると急速に外れる、という警告として捉えるのが適切です。

そのため、数時間、数日、さらに長期化するという見積もりは、普遍的な実測値ではなく、同じ機能を別の境界へ展開したときの計画上の目安です。既存の認証基盤、テスト、運用自動化、データ量、法令要件によって幅は大きく変わります。見積もりでは機能を数えるだけでなく、境界ごとの状態、失敗経路、移行、監査を列挙します。

  • 見積もりの単位を画面数から、境界・状態・失敗経路へ移す。
  • 許可ケースだけでなく、別テナント・別ブランドへの拒否ケースを同じ数だけ確認する。
  • 数時間・数日・長期化という期間は目安であり、既存の基盤と検証力で変動する。
見積もり時に数える検証軸の例
verification_surface =
  accounts
  x memberships
  x tenants
  x roles
  x feature_entitlements
  x environments
  x failure_modes

estimate_work =
  data_model + authorization + UI_states
  + background_jobs + migration + operations + tests

# The product is not a calendar promise.
# It is a reminder to count boundaries, not screens.

アカウント、所属、テナント、ブランドを一つの表へ押し込まない

設計の最初に避けたいのは、usersテーブルへorganization_id、role、brand_idを直接持たせ、後から例外を追加する方法です。一人の利用者が複数テナントへ所属できない、ブランド管理者が配下テナントを管理できない、退会後も監査履歴の主体を参照できる、といった制約が後から表面化します。関係を別エンティティとして持ち、業務上の所属と本人の識別を分けます。

Accountは本人の識別、Tenantはデータと契約の境界、MembershipはAccountがTenantへ参加する関係、RoleAssignmentはその関係上の権限、Brandは表示と提供主体の境界を表します。プラットフォーム運営者の権限は、顧客テナントのロールへ混ぜず、サポート操作や監査閲覧に期限と理由を付けた別の委任として記録します。

データベースでは、tenant_idのnullを『全体共有』の意味に使わない方が安全です。共有データはplatformスコープのテーブルへ分け、テナントデータは必須のtenant_idと複合一意制約を持たせます。PostgreSQLのRow-Level Securityを使う場合も、接続へ設定したテナント文脈が認証済みの要求と一致することをアプリケーション側で確認します。

  • AccountとMembershipを分け、複数テナント所属と退会履歴を表現する。
  • RoleAssignmentに対象スコープと期限を持たせ、恒久的な管理権限を増やさない。
  • BrandとTenantの関係を別に持ち、ブランド変更でデータ所有者を暗黙に変えない。
  • 監査イベントには主体、対象テナント、理由、時刻、相関IDを残す。
最小のデータモデル例(概念スキーマ)
accounts(id, identity_key, status, created_at)
tenants(id, status, plan_key, data_region, created_at)
memberships(account_id, tenant_id, status, joined_at, left_at)
role_assignments(membership_id, role_key, scope, expires_at)
brands(id, owner_account_id, default_policy_id, domain_status)
brand_tenants(brand_id, tenant_id)
audit_events(id, actor_account_id, tenant_id, action, reason, occurred_at)

# tenant-scoped records must carry tenant_id and be checked at every boundary.
# platform-scoped records live in a separate namespace.

認証済みのアカウントから、検証済みのテナント文脈を作る

ログインで得たIDトークンやセッションは、本人の識別を示す材料です。そこへリクエスト本文のtenant_idを加えただけで認可を完了してはいけません。サーバーはセッションのアカウントIDから有効なMembershipを読み、利用者が選択したテナントがその一覧に含まれること、Membershipが停止されていないこと、必要なRoleAssignmentが有効であることを確認してから、短いリクエスト文脈を作ります。

URL、body、Cookie、キャッシュキーのどこか一つだけにテナントを依存させると、経路ごとの取り違えが起きます。認証済みアカウント、選択テナント、ブランド、操作、対象リソースを一つのpolicy入力へまとめ、policy engineまたは明示的な認可関数で判定します。管理者によるサポート操作は、本人の通常セッションと別の操作理由・期限・対象範囲を要求します。

セッションへtenant_idを固定しても、Membershipの停止やロール期限切れは即時に反映できません。重要な操作では、キャッシュ済みセッションだけでなく、短いTTLの権限参照やバージョン番号を確認します。権限変更時にはsession_versionを更新し、古いセッションを再認証または再選択へ戻す設計が、境界を狭くします。

  • 本文のtenant_idを認可の根拠にせず、Membershipから利用可能なテナントを確定する。
  • ドメイン、URL、body、キャッシュキーのテナント情報を相互に照合する。
  • 権限変更時のsession_versionや短いTTLで、古いセッションの残存を抑える。
  • 代理サポートは通常の顧客管理者ロールと分け、理由・期限・対象を監査する。
リクエスト認可の擬似コード
const account = requireAuthenticatedAccount(request);
const tenant = await resolveTenantFromMembership(account.id, request);
const brand = await resolveBrandForTenant(tenant.id, request.host);

const decision = authorize({
  account,
  tenant,
  brand,
  action: request.action,
  resource: request.resource,
  sessionVersion: request.session.version,
});

if (!decision.allowed) throw new ForbiddenError(decision.reason);
return executeWithTenantContext({ account, tenant, brand });

分離はデータベースだけでなく、非同期処理と運用面まで続きます

マルチテナントの事故は、主テーブルのSELECT文より周辺機能で起きやすくなります。Redisのキーにtenant_idがない、検索インデックスのdocument_idが全体で衝突する、R2やS3のパスにブランドが含まれない、メールの再送ジョブが別テナントの宛先を参照する、といった経路を一つずつ確認します。ライブラリへtenant contextを渡すだけでなく、contextがないと失敗する既定値を作ります。

キューのメッセージにはtenant_id、brand_id、actor_id、schema_version、correlation_id、idempotency_keyを持たせ、消費側で再認可します。受付時に許可されていても、実行時にはMembershipが停止されている可能性があります。再試行やDead Letter Queueから戻す操作も、どのテナントのデータを扱うかを表示し、運営者の権限で明示的に承認します。

監視とバックアップもスコープを意識します。エラー率やキュー滞留をテナント別・ブランド別に集計できる一方、ログへ本文や個人情報を出しすぎないようにします。削除要求では、データ本体、ファイル、検索インデックス、バックアップ、監査証跡の保持要件を分け、削除完了を一つのフラグだけで表現しません。

  • DB、キャッシュ、検索、オブジェクトストレージ、メール、キューで同じスコープを強制する。
  • 非同期処理は受付時と実行時の二段階で認可し、再試行を冪等にする。
  • 監視の集計軸とログの秘匿範囲を、テナント・ブランドの契約に合わせる。
  • 削除・エクスポート・バックアップ復元を、テナント単位で検証する。

次の階層へ進む条件を、実装前に決めておく

最初からホワイトレーベルを目指すと、まだ存在しない販売形態のために、ブランド設定、委任管理、ドメイン、請求、サポート、移行の仕組みを先に抱えます。反対に、シングルテナントの前提をコードへ固定しすぎると、後からtenant_idやMembershipを追加する移行が大きくなります。先回りの全部入りと、将来を考えない固定化の間で、次に必要な境界だけを準備します。

シングルテナントの段階では、AccountとTenantを概念上は分けつつ、運用を一つのテナントへ限定できます。Membership、監査イベント、tenant-scopedなRepository境界を先に作っておけば、共有データベースへ移る時に業務ロジックを全面的に書き換えずに済みます。ホワイトレーベルへ進まない場合も、この分離は退会、部署追加、権限監査に役立ちます。

段階を上げる判断は、顧客数の見込みだけでなく、運用能力と検証能力で行います。テナント分離の自動テスト、権限変更の監査、バックアップ復元、レート制限、障害時の縮退運転、サポートの責任境界が整っていないなら、ブランド機能を増やす前に基盤を整える方が、長期の手戻りを減らせます。

  • 現在の階層で不要な委任管理やブランド設定を、先に汎用化しない。
  • ただしAccount、Tenant、Membership、監査の概念境界は早期に固定し、移行の余地を残す。
  • 次の階層へ進む条件を、テスト・復元・監査・運用の証拠で定義する。
  • ゲート未達を失敗ではなく、現在の範囲を守る設計判断として記録する。
段階導入のゲート例
single -> multi gate
  - every tenant record has a non-null tenant_id
  - cross-tenant read/write tests are automated
  - cache, search, files and jobs carry tenant context
  - backup restore and tenant export are repeatable

multi -> white-label gate
  - brand / tenant ownership is explicit
  - delegated administration has scope, expiry and audit
  - domain, mail, policy and support boundaries are tested
  - per-brand limits and incident response are observable

if a gate is not met: keep the current level and record why.

共有化には、性能・費用・障害範囲のトレードオフがあります

マルチテナントは、実行環境や運用を共有しやすく、利用量に応じて資源を配分できます。一方、一つのテナントの大量処理が共有キューやDB接続を占有すると、別のテナントの応答が遅くなります。テナント単位のrate limit、同時実行数、ストレージ上限、優先度、課金メーターを設け、共有の前提を計測します。

シングルテナントは、障害やデータ削除の範囲を狭くしやすい反面、環境数が増えるほどパッチ適用、監視、バックアップ、鍵の更新が個別になります。専用性が本当に必要か、共有しても契約・規制・性能の条件を満たせるかをエンティティごとに評価し、全体を一括で専用または共有に決めない方が柔軟です。

ホワイトレーベルでは、ブランドの独立性を高めるほど、設定の組み合わせと問い合わせ窓口が増えます。テーマの差し替えだけでなく、利用規約の版、通知テンプレート、メール送信ドメイン、管理者の責任、障害告知の経路がブランドごとに違うなら、それぞれを設定・監査・検証の対象にします。設定で吸収できない業務差を、巨大な条件分岐へ押し込まないことも重要です。

  • 共有化の利益: 資源利用率、共通修正、監視・運用の集約。
  • 共有化の負担: ノイジーネイバー、境界バグ、共通障害、契約別の制限。
  • 専用化の利益: 障害・削除・性能の範囲を明確にしやすい。
  • 専用化の負担: 環境数に比例するパッチ、監視、バックアップ、鍵管理。

検証は、許可テストより境界を越えないテストを厚くする

アカウント管理のテストでは、管理者が招待できる、利用者が自分のデータを読める、といった許可ケースが目立ちます。マルチテナント以降は、同じアカウントが別テナントのURLへアクセスした時、期限切れのMembershipでAPIを再送した時、キューを再試行した時、ブランドのドメインを直接書き換えた時に、正しく拒否できるかを優先します。

テストデータは、少なくとも二つのテナント、二つのブランド、複数のMembership状態、期限切れのロール、停止済みアカウント、運営者の代理操作を含めます。DBのUnit testだけでなく、API、画面遷移、メール、ファイル、ジョブ、検索、監査ログまで同じシナリオを通します。テストの合否はステータスコードだけでなく、別スコープのデータが一件も返らないことを検査します。

本番運用では、tenant_idやbrand_idを含むメトリクスを個人情報と混同しない形で集計し、異常な横断アクセスや拒否率の変化を検知します。認可の変更、代理アクセス、エクスポート、削除、復元は、後から誰が何をしたか再構成できる監査証跡を残します。検証が自動化できない領域は、階層を上げる前提条件として明示します。

  • 二つ以上のテナント・ブランドを使い、許可と拒否を同じシナリオで検証する。
  • セッション、API、画面、キャッシュ、検索、ファイル、ジョブの全経路を横断する。
  • 拒否結果だけでなく、レスポンス・ログ・副作用に別スコープの情報がないことを確認する。
  • 代理アクセス、権限変更、削除、復元を監査証跡から再構成できるようにする。

作る範囲を決めることが、アカウント設計の最初の仕事です

シングルテナント、マルチテナント、ホワイトレーベルは、同じ機能を少しずつ拡張する三つの名前ではありません。データの所有者、管理者の委任範囲、ブランドの責務、障害と削除の単位、検証する組み合わせが増える三つの設計段階です。だから、シングルテナントなら数時間で完成する試作が、マルチテナントでは数日、ホワイトレーベルではさらに長期化するリスクがあります。

この期間差を小さく見せるために、最初から全部の設定項目を作る必要はありません。Account、Tenant、Membership、Role、Brandの境界を言葉とデータモデルで分け、今使わない階層は明示的に無効にします。次の階層へ進む条件をテスト、監査、復元、運用の証拠として残し、条件を満たさない間は現在の範囲を守る方が、結果として開発と保守の予測可能性を高めます。

アカウント管理で最初に決めるべき問いは、『将来どこまで売るか』だけではありません。誰のデータを、誰が、どの期間、どの理由で扱える必要があるか。その答えが一つのテナントで完結するなら、不要な共有化を急がない。複数テナントやブランド委任が本当に必要なら、増える境界を先に数え、実装・検証・運用の期間を計画へ含める。この順序が、仕組みを作る範囲を適切に決めるための判断軸になります。

Technical documents

技術文書を増やしていきます。

AI、クラウド、業務アプリ開発、要件定義、運用設計に関する考え方を、今後も文書として整理します。

技術文書一覧へ