マルチテナントデータベースがセキュリティとパフォーマンスに与える影響、主要なリスク(分離、ノイジーネイバー)、およびテナントを安全かつ高速に保つための実践的な対策を学びます。

マルチテナントデータベースとは、多数の顧客(テナント)が同じデータベースシステムを共有する構成です—同じデータベースサーバ、同じストレージ、しばしば同じスキーマを使い、アプリケーション側が各テナントが自分のデータにしかアクセスできないように保証します。
アパートの建物のように考えてください:建物の構造や設備は共有しますが、各テナントは自分の鍵のかかった部屋を持っています。
シングルテナントでは、各顧客に専用のデータベースリソース(例えば専用のデータベースインスタンスやサーバ)が割り当てられます。分離は単純で分かりやすいですが、顧客数が増えるとコストや運用負荷が高くなります。
マルチテナンシーではインフラを共有するため効率的ですが、境界を強制する設計が必要になります。
SaaS企業がマルチテナンシーを採用する実務的理由は:
マルチテナンシー自体が自動的に「安全」や「高速」になるわけではありません。結果は、テナントをどのように分離するか(スキーマ、行、あるいはデータベース単位か)、アクセス制御の実装、暗号鍵の扱い方、あるテナントの負荷が他を遅くしないようにする手段などの選択によって決まります。
以降はこれら設計上の選択に焦点を当てます—マルチテナントシステムではセキュリティとパフォーマンスは設計して作る機能です。
マルチテナンシーは単一の設計ではなく、どれだけ密にインフラを共有するかのスペクトラムです。選んだモデルが分離の境界を定め、それがデータベースのセキュリティ、パフォーマンス分離、日常運用に直接影響します。
各テナントが専用のデータベースを持ちます(同じサーバやクラスタ上に配置されることもあります)。
分離境界: データベース自体。クロステナントアクセスは通常データベース境界を越える必要があり、最もクリーンな分離方法です。
運用上のトレードオフ: スケールすると運用が重くなります。アップグレードやスキーマ移行を何千回も実行する必要があるかもしれませんし、接続プーリングが複雑になることもあります。テナント単位のバックアップ/復元は分かりやすいですが、ストレージや管理オーバーヘッドが速く増えます。
セキュリティ&チューニング: 顧客ごとにセキュリティとチューニングを行いやすく、コンプライアンス要件が異なる場合に適しています。
同じデータベースを共有するが、各テナントは独自のスキーマを持ちます。
分離境界: スキーマ。意味のある分離ですが、正しい権限設定とツールが前提です。
運用上のトレードオフ: マイグレーションは繰り返されますが、データベースごとに分けるより軽めです。バックアップは難しくなることがあり、多くのツールはデータベースを単位にバックアップするため、テナント単位の操作ではスキーマレベルのエクスポートが必要になります。
セキュリティ&チューニング: 共有テーブルより分離しやすいですが、権限や間違ったスキーマ参照を避けるための厳格さが求められます。
すべてのテナントが同じデータベースとスキーマを共有するが、各テナントに対して別のテーブルを作る(例:orders_tenant123)。
分離境界: テーブルセット。少数のテナントでは機能しますがスケールしにくい:メタデータの増大、マイグレーションスクリプトの複雑化、クエリ計画の劣化などが起きます。
セキュリティ&チューニング: 権限は細かく設定できますが運用が煩雑になりやすく、新機能追加時にミスが発生しやすいです。
すべてのテナントが同じテーブルを共有し、tenant_id カラムで区別します。
分離境界: クエリ層やアクセス制御レイヤ(一般に行レベルセキュリティ)。スキーマを一つだけ管理すれば良いため運用は効率的ですが、データベースのセキュリティとパフォーマンス分離の要件は最も厳しくなります。
セキュリティ&チューニング: 全てのクエリがテナントを意識している必要があり、ノイジーネイバー問題が起きやすく、リソーススロットリングや慎重なインデックス設計が不可欠です。
一つの有用なルール:共有度が高いほどアップグレードは簡単になりますが、テナント分離とパフォーマンス分離のためにより厳密な設計が必要になります。
マルチテナンシーは単に「複数の顧客が一つのデータベースにいる」という状態ではありません。脅威モデルが変わり、最大のリスクは外部からの突破よりも「認可されたユーザーが誤って(あるいは意図的に)別テナントのデータを見てしまうこと」へシフトします。
認証は「あなたは誰か?」に答えます。認可は「何にアクセスできるか?」に答えます。マルチテナントデータベースではテナントコンテキスト(tenant_id, account_id, org_id など)を認可時に必ず適用し、オプションのフィルタとして扱ってはいけません。
よくある誤りは、ユーザーが認証されテナントが「分かっている」ならアプリが自動的にクエリを分離してくれるだろうと期待することです。実際には分離は明示的で一貫した制御点(データベースポリシーや必須のクエリレイヤなど)で強制する必要があります。
最も単純で重要なルール:すべてのSELECT/UPDATE/DELETE、バックグラウンドジョブ、ETL、管理ツールの操作は正確に1つのテナントにスコープされていなければならない。
テナントスコープがオプションだと、いずれどこかで抜け落ちます。
クロステナント漏洩は小さな日常的なミスから起きることが多いです:
tenant_id をバインドするテストは通常、データ量が小さく前提がクリーンです。本番では並列処理、リトライ、キャッシュ、複数テナントが混ざったデータ、エッジケースが発生します。テストで一つのテナントだけが存在するために通ってしまう機能があります。最も安全な設計は「スコープされていないクエリを書けない」ようにしておくことです。レビュアに毎回頼る設計は脆弱です。
マルチテナント環境の核心的リスクは単純です:テナントでフィルタし忘れたクエリが別テナントのデータを露呈してしまうこと。強い分離コントロールはミスを前提とし、それを無害化します。
テナント所有のレコードには必ずテナント識別子(例:tenant_id)を持たせ、アクセス層は常にこれで読み書きをスコープするべきです。
実用的パターンは「テナントコンテキストを先に決める」こと:アプリはサブドメイン、組織ID、トークンのclaimsなどからテナントを解決し、リクエストコンテキストに保存して、データアクセスコードはそのコンテキストがないと動かないようにします。
有用なガードレール:
tenant_id を含める(テナント間の衝突を防ぐため)tenant_id を含めて、クロステナントな関係が勝手にできないようにするPostgreSQLなどがサポートする場合、行レベルセキュリティでテナントチェックをデータベース側に移せます。ポリシーは現在のテナントに一致する行だけを可視化するよう制限できます。
これにより「開発者が毎回WHERE句を忘れないことに賭ける」必要が減り、ある種のインジェクションやORMの誤用からも保護されます。RLSは“追加の錠”として使い、唯一の防御にしないでください。
テナントの感度が高い、またはコンプライアンス要件が厳しい場合、スキーマあるいはデータベースで分けることで被害範囲を小さくできますが、運用負荷は増えます。
デフォルトを「アクセス不可」にする設計:
これらを組み合わせると、何かが滑ったときに被害が限定されます。
暗号化は他の分離層が失敗したときにも有効なコントロールの一つです。共有ストアではデータを「移動中」「保存時」、そして「アプリがどのテナントとして動いているかを証明する際」に保護することが目標です。
通信中は、クライアント→API、API→データベース、内部サービス呼び出しのすべてでTLSを必須にしてください。可能ならデータベース側で非TLS接続を拒否する設定にして、一時的な例外が恒久化しないようにします。
保存時は、データベースやストレージレベルの暗号化(管理されたディスク暗号化、TDE、暗号化バックアップ)を使います。これはメディア紛失やスナップショット露出、インフラ侵害の一部リスクを軽減しますが、バグのあるクエリが別テナントの行を返すことは防げません。
単一の共有鍵は運用が単純ですが、鍵が漏れた場合の被害範囲が大きくなります。テナント毎の鍵は被害範囲を限定でき、顧客要件にも合うことがありますが、鍵のライフサイクル管理やローテーション、テナントが鍵を無効にした場合のサポートワークフローなど運用が複雑になります。
実用的な折衷はエンベロープ暗号化:マスター鍵でテナント毎のデータ鍵を暗号化し、ローテーションを管理しやすくする方法です。
データベース資格情報はシークレットマネージャに保管し、長期間置かれた環境変数にするべきではありません。短期の資格情報や自動ローテーションを好み、サービスロールでアクセスを限定して、一つのコンポーネントの侵害が全てに繋がらないようにします。
テナントIDをセキュリティクリティカルに扱い、クライアントから送られる生のテナントIDをそのまま信頼しないでください。テナントコンテキストは署名トークンやサーバー側の認可チェックに紐付け、各リクエストで検証してください。
マルチテナンシーは「通常値」を変えます。単にデータベースを監視するのではなく、共有システムを複数のテナントが使っており、あるミスがクロステナント露出になる可能性があることを前提に監視する必要があります。優れた監査性とモニタリングはインシデントの発生確率と被害範囲を減らします。
少なくとも、テナントデータを読み、変更し、またはアクセス権を与える可能性のあるすべての操作をログに残してください。最も有用な監査イベントは次に答えます:
また、テナント作成や分離ポリシー変更、RLSルールの変更、鍵のローテーション、接続文字列の変更といった管理操作もログに残します。
モニタリングは通常のSaaS利用では起きにくいパターンを検出すべきです:
アラートには実行可能なランブックを紐付けてください:確認手順、封じ込め方法、連絡すべき担当者。
特権アクセスは本番変更として扱います。最小権限ロール、短期資格情報、重要操作の承認フローを利用してください。緊急時用のブレイクガラスアカウントは別管理の資格情報、必須のチケット/承認、時間制限されたアクセス、強化されたログを設けます。
保持期間はコンプライアンスと調査ニーズに基づいて設定しますが、ログへのアクセスはテナント単位で制限し、サポートスタッフが自分のテナントのログだけを見られるようにします。顧客が監査エクスポートを要求する場合は生ログではなく、テナントでフィルタしたレポートを提供します。
マルチテナンシーは多くの顧客に同じデータベースインフラを共有させることで効率を高めますが、トレードオフとしてパフォーマンスは共有体験になります:あるテナントの振る舞いが他のテナントに影響を与える可能性があります。
「ノイジーネイバー」は、あるテナントの活動が非常に重かったりピーキーだったりして、共有リソースを必要以上に消費してしまうテナントです。データベースが壊れているわけではなく、そのテナントの仕事を処理している間に他のテナントの待ち時間が増えるのです。
アパートの水圧に例えると分かりやすい:ある部屋が同時にシャワーと洗濯機を回すと水圧が下がり、他の住人が影響を受ける、という状況です。
たとえ各テナントが別の行やスキーマを持っていても、多くのパフォーマンスに重要な資源は共有されます:
これらの共有プールが飽和すると全体のレイテンシが上がります。
多くのSaaSワークロードはバーストで来ます:インポート、月次レポート、マーケティングキャンペーン、毎時のcronなど。
バーストはデータベース内で渋滞を生みます:
バーストが数分でも続けばキューが枯渇するまで影響が続きます。
顧客から見た症状はランダムで不公平に感じられます。一般的な症状:
これらは「もっとハードウェアを増やす」だけでは根本解決にならないことが多く、パフォーマンス分離技術が必要な早期警告です。
マルチテナンシーは「一つの顧客がデータベース容量を他から借りすぎない」ことが重要です。リソース分離は重いテナントが他を遅らせないためのガードレール群です。
一般的な失敗モードは無制限な接続です:あるテナントのスパイクで何百ものセッションが開き、データベースを枯渇させることがあります。
二箇所でハードキャップを設定します:
データベース側で直接「テナントごとの接続」を強制できなくても、各テナントを専用プールやプールパーティション経由でルーティングすることで近似できます。
レート制限は時間を通じた公平性をもたらします。エッジ(APIゲートウェイ/アプリ)近くで適用し、可能な場合はデータベース側(リソースグループやワークロード管理)でも行います。
例:
データベースを「暴走クエリ」から守る:
これらはフェイル時に明確なエラーと再試行/バックオフの指示を返すべきです。
読み取り負荷の多いトラフィックをプライマリから切り離します:
目的は単なる速度向上だけでなく、ロックやCPU競合を減らし、ノイジーネイバーが他に影響を与える経路を減らすことです。
マルチテナントのパフォーマンス問題は「データベースが遅い」という見え方をしますが、原因の多くはデータモデル(テナントデータのキー、フィルタ、インデックス、物理レイアウト)にあります。良いモデリングはテナントスコープのクエリを自然に速くし、悪い設計はデータベースに余計な仕事をさせます。
多くのSaaSクエリはテナント識別子を含みます。これを明示的にモデル化し(例:tenant_id)、インデックスはそれを先頭に置くように設計します。実務上、(tenant_id, created_at) や (tenant_id, status) のような複合インデックスは、単独で created_at や status にインデックスを張るよりはるかに有用です。
一意性も同様です:メールアドレスがテナント内でのみユニークなら (tenant_id, email) で制約を設けるべきで、グローバルな email 制約は適切ではありません。
遅いクエリの一般的なパターンは、テナントフィルタを忘れて大きなテーブルにまたがってスキャンすることです。
安全な経路を簡単にする方法:
パーティショニングは各クエリが見るデータ量を減らせます。テナントが大きく不均一な場合はテナントでパーティションを切ることを検討してください。アクセスが主に最近のデータである場合は時間でパーティションを切り、各パーティション内で tenant_id を先頭にしたインデックスを使います。
単一データベースでピークトラフィックを捌けない、あるいは1つのテナントの負荷が他全員を脅かす場合はシャーディングを検討します。
“ホットテナント”は読/書のボリューム、ロック競合、大きなインデックスで不均衡を生みます。
テナントごとのクエリ時間、読み取り行数、書き込み率を追跡して発見します。1つのテナントが支配的であれば分離する:別シャード/データベースへ移す、大きなテーブルをテナント別に分割する、専用キャッシュやレート制限を導入して他のテナントの速度を守る、などです。
マルチテナンシーはデータベース自体が原因で失敗することはまれで、多くは日々の運用での小さな不整合が積み重なってセキュリティギャップやパフォーマンス劣化を招きます。目標は「安全な道」をすべての変更、ジョブ、デプロイでデフォルトにすることです。
単一の正規テナント識別子(例:tenant_id)を選び、テーブル、インデックス、ログ、APIで一貫して使ってください。整合性があるほどセキュリティミスやパフォーマンスの驚きが減ります。
実用的な対策:
tenant_id を要求する(クエリ、リポジトリ、ORMスコープ)tenant_id を先頭にした複合インデックスを追加するtenant_id を含む外部キーやチェック制約)を使って不正な書き込みを早期に検出する非同期ワーカーはしばしばテナントコンテキストを失い、クロステナント事故の原因になります。
有効な運用パターン:
tenant_id を明示的に渡す(環境コンテキストに頼らない)tenant_id をログに残すスキーマやデータマイグレーションは完璧な同時ロールアウトを前提にせずに進められるべきです。
ローリングでの変更を使う:
意図的に別テナントのデータにアクセスしようとするネガティブテストを追加し、これらをリリースブロッカーにしてください。
例:
tenant_id を持つバックグラウンドジョブがハードに失敗することをテストバックアップは「データベースをコピーする」と説明するのは簡単ですが、マルチテナント環境では安全に実行するのが意外に難しいです。多くの顧客がテーブルを共有している瞬間、1つのテナントだけを復元する方法や他テナントを露出させずに復元する計画が必要になります。
フルデータベースバックアップは災害復旧の基礎ですが、日常のサポートケースには不十分なことが多いです。一般的なアプローチ:
tenant_idでフィルタした論理ダンプ):単一テナントの復元用論理エクスポートに頼る場合、エクスポートジョブ自体を本番コードと同等に扱い、WHERE句を一度書いて終わりにするのではなく、RLS等で分離を強制するなどして安全性を確保してください。
エクスポートや削除はセキュリティとパフォーマンスに関わるテナント単位の操作です。以下を持つ再現可能で監査可能なワークフローを構築してください:
最大のリスクはハッカーではなく急いだオペレータです。人的ミスを減らすガードレール:
tenant_id の分布を検証する災害復旧訓練後は「アプリが立ち上がった」で終わらせないでください。テナント分離を確認する自動化チェックを実行:テナント横断サンプルクエリ、監査ログのレビュー、暗号鍵やアクセスロールが正しくスコープされているかのスポット検証などを行います。
マルチテナンシーは多くのSaaSでデフォルトとして有効ですが、永続的な決定ではありません。プロダクトや顧客構成が進化すると、「1つの共有データストア」アプローチがビジネスリスクや開発の足かせになることがあります。
以下が継続的に見られる場合、より分離を進める検討を:
より分離すると一般にインフラコストが上がり、**運用オーバーヘッド(マイグレーション、監視、オンコール)**が増え、リリース調整が必要になります。対価として、パフォーマンス保証が明確になり、コンプライアンス上の議論がしやすくなります。
分離オプションを評価するなら、/blog の関連ガイドを確認するか、/pricing でプランとデプロイオプションを比較してください。
すぐにSaaSをプロトタイプしてマルチテナンシーの仮定(テナントスコーピング、RLSに優しいスキーマ、スロットリング、運用ワークフロー)を早期に試したい場合は、Koder.ai のようなvibe-codingプラットフォームを使うと、チャットから React + Go + PostgreSQL の動くアプリを立ち上げ、計画モードで反復し、スナップショットとロールバックでデプロイし、準備が整ったらソースコードをエクスポートして本番アーキテクチャを強化できます。
マルチテナントデータベースは、複数の顧客が同じデータベース基盤(しばしば同じスキーマ)を共有しつつ、アプリケーションやデータベース側でそれぞれのテナントが自分のデータにのみアクセスできるように強制する仕組みです。核心は、すべての読み書きで厳密なテナントスコープを適用することです。
マルチテナンシーが選ばれる主な理由は次の通りです:
トレードオフとして、分離とパフォーマンスのガードレールを意図的に設計する必要があります。
一般的なモデル(分離が強い順に):
tenant_idカラム): 運用は最も簡単だがセキュリティやチューニングは難しい。選択は分離境界と運用負担を決めます。
リスクは外部攻撃だけではなく、日常的なミスで別テナントのデータにアクセスしてしまうことに移ります。tenant_id のようなテナントコンテキストは、オーセンティケーションの結果ではなく、常に認可の要件として扱う必要があります。実運用では並行処理、キャッシュ、リトライ、バッチ処理などがあり、これらを前提に設計しなければなりません。
典型的な原因は次のとおりです:
tenant_id をバインドしている設計上、スコープされていないクエリが実行されにくい仕組みにすることが重要です。
RLS(行レベルセキュリティ)は、データベース側にテナントチェックを移せる強力な手段です。SELECT/UPDATE/DELETEを現在のテナントに一致する行だけに制限するポリシーを定義できます。ただし、RLSは“もう一つの鍵”と考え、アプリ層でのスコーピング、最小権限、十分なテストと組み合わせて使うべきです。単独に頼らないでください。
実用的なベースライン例:
tenant_id を持たせるtenant_id を含む複合ユニーク制約や外部キーを使うミスが起きても安全に失敗することを目指します。
暗号化は失敗したときのダメージを限定する手段です:
また、クライアントから送られる生のテナントIDを信頼せず、署名トークンやサーバー側での検証と紐付けを行ってください。
ノイジーネイバーは、あるテナントの負荷(CPU、メモリ、I/O、接続など)が共有リソースを使い切り、他のテナントのレイテンシを上げる現象です。対策の例:
目標はスループットだけでなく“公平性”です。
以下の状況が継続して起きるなら分離を強める検討を:
ハイブリッドの選択肢:トップティアだけ専用データベースに切り出す、共有と専用のプランを用意する、トランザクションは共有で分析だけ別にする等です。