マルチテナンシー
1 つのシステムで複数のテナント (顧客) のデータを安全に分離して提供するアーキテクチャ
マルチテナンシーとは
マルチテナンシーは、1 つのシステムで複数のテナント (顧客) のデータを安全に分離して提供するアーキテクチャである。SaaS では標準的な設計で、テナントごとに環境を建てずに空き容量を共有することで、テナント 1 件あたりの固定費と運用対象の数を抑えることが狙いになる。その代償として、分離はインフラの境界ではなくアプリケーションとデータ設計の中に作り込むことになり、1 箇所のテナント ID の付け忘れが他社データの露出に直結する。
分離モデル
分離の粒度は 3 段階で整理できる。表のコストは、テナント数に比例して増えるリソースがどれだけあるかを表している。
| モデル | 分離レベル | コスト | 例 |
|---|---|---|---|
| サイロ | テナントごとに独立リソース | 高 | テナントごとに DynamoDB テーブル |
| プール | 全テナントが共有リソース | 低 | 1 つの DynamoDB テーブルに全テナント |
| ブリッジ | 一部共有、一部分離 | 中 | 共有 Lambda + テナント別テーブル |
リレーショナルデータベースに当てはめると、サイロはテナントごとに別インスタンスまたは別データベース、ブリッジは同一データベース内でテナントごとにスキーマを分ける形、プールは共通テーブルに tenant_id 列を持たせる形になる。プールで行レベルセキュリティ (PostgreSQL の Row-Level Security など) を使う場合は、テーブル所有者と superuser、BYPASSRLS 属性を持つロールがポリシーを素通りする点に注意が必要である。アプリケーションが所有者ロールで接続していると、ポリシーを書いたつもりで全テナントの行が見える状態になる。所有者にも適用させるには ALTER TABLE ... FORCE ROW LEVEL SECURITY を明示する。
DynamoDB でのテナント分離 (プールモデル)
プールモデルでは、パーティションキーの先頭にテナント ID を埋め込むことで、キー条件だけで他テナントの行に届かない構造にする。テナント ID を属性として持たせてフィルタで絞る形にすると、条件の付け忘れがそのまま全テナント参照になるため、キーに埋める設計が要になる。
// パーティションキーにテナント ID を含める
await db.put({
TableName: 'items',
Item: {
PK: `TENANT#${tenantId}`,
SK: `ITEM#${itemId}`,
data: { ... },
},
});
// テナントのデータのみ取得
const items = await db.query({
TableName: 'items',
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: { ':pk': `TENANT#${tenantId}` },
});
この構造の上で、IAM ポリシーの Condition にテナント ID を書き込むと、アプリケーションのコードが誤っても他テナントの行を読めなくなる。DynamoDB では dynamodb:LeadingKeys 条件キーがテーブルの先頭キー属性 (パーティションキー) を表し、この条件キーを使うときは ForAllValues 修飾子が必須になる。実装上は、テナントごとに固定のポリシーを持つ利用者を作るのではなく、認証後にテナント ID を埋めたセッションポリシー付きでロールを引き受け、そのテナント限定の一時認証情報でデータへアクセスする形が扱いやすい。
ノイジーネイバー問題
共有リソースでは、あるテナントの負荷が他テナントの応答時間に漏れ出す。DynamoDB で効いてくるのは、テーブル全体の容量とは別に 1 パーティションあたりの上限があることで、各パーティションは毎秒 3,000 読み込みユニットと毎秒 1,000 書き込みユニットを上限として設計されている。テナント ID をパーティションキーにする設計は分離には効くが、その分 1 テナントの負荷が 1 パーティションに集まるため、巨大テナントはこの上限に先に当たる。
テナント A: 大量のリクエスト → DynamoDB のスロットリング
テナント B: 通常のリクエスト → テナント A の影響で遅延
| 対策 | 説明 |
|---|---|
| テナントごとのレート制限 | API Gateway の Usage Plan |
| DynamoDB のパーティション設計 | テナント ID をパーティションキーに |
| Lambda の同時実行制限 | 予約同時実行数は関数単位の設定であり、テナント別に上限を分けたいなら関数自体を分けるか、キューを挟んで取り出し側で絞る。予約は上限と同時に下限としても働き、確保した分は他の関数から使えなくなる |
テナント識別
テナントの識別には、JWT のクレームに tenant_id を含める方法、tenant-a.example.com のようなサブドメイン方式、/api/tenants/{tenantId}/ のようなパスプレフィックス方式、テナントごとに API キーを発行する方法がある。
テナント ID をどこから取るかは分離の強度と直結する。パスやクエリの値をそのまま信じると利用者が書き換えられるため、認証済みトークンのクレームを唯一の出所とし、経路上の値は照合にしか使わない形が原則になる。設計の全体像は関連書籍で体系的に追える。
この記事は役に立ちましたか?
関連用語
DynamoDB
AWS のフルマネージド NoSQL データベースで、ミリ秒単位のレイテンシとシームレスなスケーリングを提供する
IAM (概念)
Identity and Access Management の一般概念で、認証と認可を管理する仕組み
VPC
AWS 上に論理的に隔離されたプライベートネットワークを構築するサービス
Init コンテナ
Kubernetes でメインコンテナの起動前に初期化処理を実行する特殊なコンテナ
コンテナ
アプリケーションとその依存関係をパッケージ化し、環境に依存しない一貫した実行環境を提供する仮想化技術
コンテナオーケストレーション
コンテナのデプロイ、スケーリング、ネットワーキングを自動管理するプラットフォーム
関連する記事
データベース本ガイド - SQL から設計まで学べる技術書の選び方
データベースの基礎から設計、パフォーマンスチューニングまで学べる技術書の選び方と学習順序を紹介します。
セキュリティ本ガイド - Web 開発者が読むべき技術書の選び方
Web セキュリティの基礎から実践まで学べる技術書の選び方マトリクスと、読了後にやるべき 3 つのアクションを紹介します。
体系的に学ぶ 安全な Web アプリケーションの作り方 第 2 版は初版と何が違うか - 買い直し判断ガイド
通称「徳丸本」こと体系的に学ぶ 安全な Web アプリケーションの作り方 第 2 版 (2018 年) と初版 (2011 年) の違いを出版社公表の改訂内容から整理。章の削除 / 新設 / 追加点の一覧と、初版所有者が買い直すべきかの判断基準を解説します。