⚙️

Amazon DynamoDB マルチテナント設計

2025/02/21に公開

はじめに

マルチテナント環境における、Amazon DynamoDB導入時の設計方針をデータ分離とセキュリティを中心に整理しました。

なお、本記事ではモデリングプロセスやセカンダリインデックス、キャパシティなどの詳細については解説していません。全体的な設計について詳しく知りたい方は、公式ドキュメントの参照をおすすめします。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html

設計方針

最初に設計方針の結論を述べると、データ分離にはプールモデルを適用し、一部のテナントが大量のデータを読み書きする際に発生しやすい、パーティションのホットスポットを防ぐ対策を講じます。さらに、適切なセキュリティ対策も実施します。

データ分離

データの分離方法は非機能要件に関わる重要な要素です。特に、BtoB向けのSaaSアプリケーションでは、セキュリティの重要度はさらに高まります。データ分離のアーキテクチャモデルとしては、サイロモデル・プールモデル・ブリッジモデルがあり、DynamoDBにも同様のアプローチを適用できます。

それぞれの簡潔な説明が、以下になります。

  • サイロモデル:テナント毎に完全に独立したリソースを持つ方式で、分離のレベルが最も高く、セキュリティやカスタマイズ性に優れるが、運用コストが増加する
  • プールモデル:すべてのテナントが同じリソースを共有する方式で、運用のコスト効率に優れるが、セキュリティやスケーラビリティの管理が必要になる
  • ブリッジモデル:サイロモデルとプールモデルのハイブリット型になっており、システムの一部がサイロ型、別の部分がプール型として実装されるケースがあり、分離と共有のバランスを取れるが、実装の複雑さや管理が増加する可能性がある

最初の結論で述べたとおり、プールモデルを選定します。以下では、その選定理由を解説し、プールモデルのテーブル構造も紹介します。

選定理由

以下の二つが、サイロモデルを除外した主要な理由(デメリット)です。

  • 一テナントにつき一つのテーブルを使用するため、テナント数が最大2500を超える場合(AWSアカウントチームに連絡すれば10000まで拡張可能)、テーブル作成の上限に達するため別のAWSアカウントを作成する必要がある
  • インフラ側でテナント毎の様々な設定が必要になり、その分管理コストがかかる

一つ目は、大規模なサービスを運用していると、テナント毎にテーブルが作成されるため、2500の上限にはすぐに達してしまうことが容易に想像できます。仮に上限を10000まで引き上げたとしても、再びすぐに上限に達する可能性が高く、そのたびに別のAWSアカウントを作成する必要があり、この運用は非常に手間がかかります(Linked account silo modelの形式も同様に)。

二つ目は、アプリケーション側でのテーブルの特定や、テナント毎のテーブル設定(キャパシティ、ポリシーなど)の管理、さらにどのサービスと紐づいているかの把握コストが挙げられます。これらの運用コストも、テナントの増加に伴い大きくなっていきます。

一方、サイロモデルのメリットがプールモデルのデメリットにもなるのですが、これらは対策可能であり、運用コストへの影響も限定的です。本記事ではその具体例として、パーティションのホットスポットの問題とセキュリティを取り上げます。

まとめると、サイロモデルのデメリットよりもプールモデルのデメリットの方が対策が可能で最小限に抑えられるので、サイロモデルを除外してプールモデルを選定します。

補足情報として、マルチテナントに関係なく公式ドキュメントでは、テーブルの数を最小限に抑え、単一のテーブルを使用することが推奨されています。

General design principles in Amazon DynamoDB recommend that you keep the number of tables you use to a minimum. In the majority of cases, we recommend that you consider using a single table.

引用元:https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-table-design.html

テーブル構造

プールモデルを選定した際の、テーブル構造は以下になります。

テナント毎の従業員を管理するテーブル

パーティションキー:TenantId
ソートキー:EmployeeId
プライマリキー:TenantId & EmployeeId

TenantId EmployeeId Name ...
1 10 太郎1 ...
2 3 太郎4 ...
1 21 太郎5 ...
1 4 太郎2 ...
2 10 太郎3 ...

パーティションキーにテナント識別子を、ソートキーに従業員IDを設定することで、テナント毎の従業員を管理できるようになります。

テナント毎の部署と従業員を管理するテーブル

パーティションキー:TenantId
ソートキー:Kind
プライマリキー:TenantId & Kind

TenantId Kind ...
1 Employee#10 ...
2 Employee#3 ...
1 Department#21 ...
1 Employee#4 ...
2 Department#10 ...

部署も管理対象に含め、複数のテーブルを1つのテーブルに統合して表現することも可能です。

このように、マルチテナントをプールモデルで表現できました。
ただし、選定理由の箇所でも述べたように、単純にテナント識別子をパーティションキーに設定するとこのままでは、以下の問題が発生する可能性があります。

  • 一部のテナントが大量のデータを扱うと、パーティションのホットスポットが発生する可能性があり、スロットリングがおきる
  • 他のテナントにアクセスできるバグが発生すると、セキュリティインシデントにつながる

以下で、これらの問題の対策を解説します。

ホットスポット対策

テナント識別子のみで管理すると、前述の通り一部のテナントが大量のデータを扱うと、特定のパーティションで読み書きの負荷が増大し、ホットスポットが発生してスロットリングが生じる可能性があります。この事象が発生すると、関係のないテナントにも影響を与えてしまいます。

対策として、主に以下の方法が考えられます。

  • テナント識別子にサフィックスを付与する
  • パーティションキーの見直し
  • Exponential Backoffのリトライ戦略を取る
  • インメモリデータベースを使用して、データをキャッシュさせる
  • SQSなどを導入して、流入の調整をする

既に運用している場合は、リトライ戦略やインメモリデータベース、SQSの活用により、テーブルの設計を変更せずに、ある程度スロットリングの影響を緩和できます。それでも対策が難しい場合は、パーティションキーの見直し、テナント識別子にサフィックスを付与する方法が有効です。データの分散率を向上させ、パーティションのホットスポットの影響を最小限に抑えることができるためです。

今回は、テナント識別子にサフィックスを付与して管理するテーブルを紹介します。

サフィックスを付与してテナント毎の従業員を管理するテーブル

パーティションキー:TenantId-Suffix
ソートキー:EmployeeId
プライマリキー:TenantId-Suffix & EmployeeId
サフィックス生成方法:EmployeeId % 100 + 1(1〜100で分散)

TenantId-Suffix EmployeeId Name ...
1-11 10 太郎1 ...
2-4 3 太郎4 ...
1-22 21 太郎5 ...
1-5 4 太郎2 ...
2-11 10 太郎3 ...

サフィックスの付与方法として、ランダムに 1〜100 の値を割り当てると、特定の項目を取得する際に最大100回の検索を実行する必要があります。しかし、EmployeeIdから生成したサフィックスを付与することで、1回の検索で取得できるようになります。なお、EmployeeIdを使用しない検索ユースケースでは、この方法が適用できないので、別のアプローチを検討する必要があります。

マルチテナントかどうかに関わらず、パーティションキーを決める際の考慮事項として、以下の公式記事がおすすめです。
https://aws.amazon.com/jp/blogs/database/choosing-the-right-dynamodb-partition-key/

最後にセキュリティ対策について解説していきます。

セキュリティ対策

データ分離の箇所で頭出しはしましたが、BtoB向けのSaaSアプリケーションではセキュリティが重要視されるため、今回のようにサイロモデルでデータを管理する場合、他のテナントにアクセスできてしまうバグが潜んでいると、重大なセキュリティインシデントにつながってしまいます。

PostgreSQLやMySQLでは、RLSの活用や、テナント毎にスキーマを分けるといった手法によってセキュリティ対策を行う例をよく見かけます。
https://buildersbox.corp-sansan.com/entry/2021/05/10/110000

DynamoDBでは、FGAC(Fine-Grained Access Control)という考えで、IAMポリシーを用いてユーザーやロールで特定の項目や属性に対する読み書きを制御し、セキュリティ対策を講じることができます。

以下にそのポリシーを提示します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        アクション指定
      ],
      "Resource": [
        リソース指定
      ],
      "Condition": {
        "ForAllValues:StringLike": {
          "dynamodb:LeadingKeys": [
            "{TenantId}-*"
          ]
        }
      }
    }
  ]
}

このポリシーは、リクエストに含まれるパーティションキーが特定のパターンに一致する場合にのみ、項目にアクセスできるように制御されています。指定されたテナントIDで始まるパーティションキーのみがアクセス対象となり、他のテナントのデータへのアクセスが防止されます。

具体的には、ForAllValues:StringLikeで、リクエストで指定されたすべての値が、指定した文字列パターンに一致するかどうかを検証しています。また、dynamodb:LeadingKeysはDynamoDBのパーティションキーとして使用される値を示しており、今回の例ではホットスポット対策としてTenantId-Suffixの形式を採用しているため、パターンは{TenantId}-*となっています。

また、Amazon Cognito + IAM ポリシーによってさらにセキュリティレベルを上げることができるので、組み合わせをおすすめします。

まとめ

いかがだったでしょうか。
本記事では、マルチテナント環境におけるDynamoDBの設計方針について解説しました。特に、データの分離とセキュリティは運用の中で重要なポイントとなるため、慎重に設計することをおすすめします。また、繰り返しになりますが今回ご紹介した設計方針は、他の設計方針とトレードオフを考慮しながらプロダクトに適用してください。

最後までお読みいただき、ありがとうございました!
また、DynamoDBの論文(2022)でこれまで、どのようにシステムが変遷してきたかの全体像を把握できるので、気になる方は是非読んでみてください!
https://www.usenix.org/system/files/atc22-elhemali.pdf

Discussion