DynamoDB でシングルテーブル設計を使うべきでないとき
シングルテーブル設計は DynamoDB のデフォルトのアドバイスです。1 回の Query が親とその子を返し、結合も N+1 もありません。しかしそれはトレードでもあり、硬直した不透明なスキーマと引き換えに読み取り速度を買います。そして 3 種類のワークロードは、得るもの以上に支払うことになります。
DynamoDB でシングルテーブル設計を使うべきでないのはどんなときか?
ワークロードが重い OLAP 分析、少数の無関係なエンティティへの単純な CRUD、または独立してスケールや障害が起きるエンティティである場合は、シングルテーブル設計を避けましょう。そのような場合、複数のテーブルのほうが読みやすく柔軟性も保て、エンティティをまとめて読まない限り、リクエストあたりのコストも変わりません。
- 重い分析? シングルテーブルにしないこと。オーバーロードされたキーは OLAP に不向きなので、テーブルを S3 にエクスポートし、Athena でクエリしましょう。
- 少数のアクセスパターンを持つ単純な CRUD? エンティティごとに 1 つのテーブルで問題なく、読みやすく、パフォーマンス上の代償もありません。
- 独立してスケールまたは障害が起きるエンティティ? 別々のテーブルなら、それぞれを独立してバックアップ、暗号化、スケーリングできます。
- シングルテーブルがなお勝つのは、パターンが既知で、関連していて、高ボリュームのときです。それがこの設計が作られたケースです。
シングルテーブルが実際に何を犠牲にするか知る
シングルテーブル設計は、コストを読み取りパスから外し、可読性と柔軟性に乗せます。PK/SK の背後に
5 つのエンティティ型を保持するテーブルは、読みにくく、変更しにくいです。
新しいアクセスパターンは、テーブル内のすべてのアイテム型にまたがるバックフィルを意味し得ます。新しいインデックス属性を書き込む、スロットリングされた並列 Scan です。これは
DynamoDB のマイグレーションで扱っています。アクセスパターンがその硬直さを正当化しないなら、複数のテーブルに手を伸ばしましょう。
リクエストあたりの料金はどのレイアウトでも同じです。各 GetItem は、どのテーブルに対するものでも、結果整合性で 4 KB あたり 0.5 読み取りユニットが課金されます。複数のテーブルのほうが高くつくのは、
1 つの画面がそれらのいくつかからエンティティを読むときだけです。リクエストごとに個別に切り上げられるからです。
AWS 自身の例では、4 KB 未満の 2 つのアイテムに対する 1 回のクエリは 0.5 読み取りユニット、2 回のクエリなら 1 です(データモデリングの基礎)。シングルテーブル設計のコスト表で数値を計算しています。
分析ワークロードをシングルテーブルにしない
DynamoDB は OLTP のために作られています。小さく、既知の、ポイントと範囲の読み取りです。分析は OLAP です。GROUP BY、大きな集計、データセット全体にわたるアドホックなスライシング。
2 つは反対方向に引っ張ります。
シングルテーブル設計は OLAP を悪化させます。オーバーロードされたキーと混在したエンティティ型のせいで、分析ジョブは何かを合計する前に、まずどのアイテムがどれなのかをほどかなければなりません。
SQL の出身者の反射は、稼働中のテーブルに対して集計を書くことです。DynamoDB では、それはフルの
Scan です。すべてのアイテムを読んで料金を払う、フルボリュームの
Scan の落とし穴です。
分析は別のエンジンに移しましょう。AWS の S3 へのエクスポートは "enables you to perform analytics and complex queries on your data using other AWS services such as Athena" ものです(DynamoDB の S3 へのエクスポート)。数値がどれだけ新しくある必要があるかに応じて、2 つの方法があります。
- バッチまたは 1 回限り: ポイントインタイムリカバリを有効にし(
aws dynamodb update-continuous-backups --table-name T --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true)、aws dynamodb export-table-to-point-in-timeを実行して、DynamoDB JSON ファイルを Athena でクエリします。列指向のスキャンが必要なら、AWS Glue で Parquet に変換します。エクスポートは読み取りキャパシティを消費しません。 - 継続的: Amazon Redshift とのゼロ ETL 統合は 15〜30 分ごとに更新をレプリケートし、これもポイントインタイムリカバリを必要とします。
単純な CRUD をシングルテーブルにしない
アプリが少数の無関係なエンティティを独自の ID で読み書きし、せいぜい 3 つのアクセスパターンしか持たないなら、シングルテーブルは何ももたらしません。
小さな B2B ツールの Tenants テーブルと ApiKeys テーブルを取り上げます:
| TenantId | Name | PlanTier |
|---|---|---|
| TNT-8842 | Northwind | pro |
| KeyId | TenantId | Scope |
|---|---|---|
| KEY-01H9... | TNT-8842 | read-write |
各クエリは ID による GetItem か、TenantId をキーにした GSI に対する Query です。最適化すべき親子のフェッチはないので、オーバーロードされたパーティションが勝ち取るものもありません。
2 つの明快なテーブルは、1 つの濁ったテーブルより読みやすいです。
罠は、「ベストプラクティス」だからとシングルテーブルをカーゴカルト的に真似ることです。標準的なアプローチはアクセスパターンから始めることであり、パターンが単純なら、単純な形が正しい形です。
独立してスケールまたは障害が起きるテーブルを分割する
1 つのテーブルは、1 つのバックアップ、1 つの暗号化キー、1 つのストリーム、1 つのキャパシティプールを共有します。2 つのエンティティの書き込みレートや耐久性のニーズが異なるとき、その共有された運命が負債になります。
フリート追跡システムを想像してください。車両はめったに変わりませんが、そのテレメトリは毎秒注ぎ込まれます:
| VehicleId | Make | Model | Region |
|---|---|---|---|
| VEH-204 | Volvo | FH16 | eu-west |
| VehicleId | DeviceTs | SpeedKph | Fuel |
|---|---|---|---|
| VEH-204 | 2026-06-23T10:00:01Z | 88 | 0.61 |
テレメトリは車両をキーにし、タイムスタンプをソートキーにしているので、「VEH-204 の直近 1 時間」は
1 回の Query で済み、同じ秒に報告した 2 台の車両が衝突することもありません。
2 つのテーブルなら、テレメトリの履歴を含めずに車両カタログをバックアップでき、それぞれに専用のストリームコンシューマーを持たせられます。AWS は、別々のバックアップ、暗号化キー、ストリームをマルチテーブル設計の利点として挙げています(データモデリングの基礎)。
キャパシティも分かれるため、テレメトリの書き込みバーストがカタログのプロビジョンドの書き込みを使い果たすことはありません。AWS はその裏面をコストとして数えています。各テーブルに専用のアラームが必要で、スケーリングも個別に行われます。
TTL は AWS の利点のリストにはありません。TTL はアイテムごとに働くので、1 つのテーブルでも有効期限の属性を持つテレメトリのアイテムだけが期限切れになります。
コミットする前に決定をマッピングする
ワークロードを 1 つのゲートに通しましょう。エンティティは関連していて、パターンは既知で高ボリュームか?そうでなければ、複数のテーブルです。
フローとして読んでください。一番上から始めて、最初に一致する分岐を辿ります。
いいえ / はい / はい の経路だけがシングルテーブル設計にたどり着きます。
シングル vs 複数、正面から
| 要因 | シングルテーブル | 複数テーブル |
|---|---|---|
| 関連する読み取り | 1 回の Query、結合なし | クライアント側の結合または追加の往復 |
| 可読性 | 不透明なオーバーロードされたキー | テーブルごとに 1 エンティティ、自己文書化 |
| 新しいアクセスパターン | バックフィルがすべてのアイテム型に及び得る | バックフィルは 1 エンティティのテーブルだけ |
| 分析 / OLAP | 集計の前にエンティティ型をほどく | やはりエクスポート、ただしエンティティごとにクリーン |
| 独立したスケーリング | バックアップ、キー、ストリーム、キャパシティを共有 | 別々にバックアップ、暗号化、スケーリング |
| 最適なとき | 既知で関連した高ボリュームのパターン | 無関係、進化中、または分析的 |
落とし穴と次のステップ
鏡像の間違いは過剰修正です。関連した高ボリュームのエンティティをエンティティごとのテーブルに分割し、アプリ内で SQL スタイルの結合を再構築することです。それはシングルテーブルが取り除く N+1 の罠です。アクセスパターンが求める形を選びましょう。
リレーションシップをモデル化するときは、正しいインデックス型に頼りましょう。追加する前に
GSI と LSI の比較を参照してください。複数テーブルのスキーマに対してクエリを書くときは、まず DynamoDB Expression Builder で
KeyConditionExpression を下書きしましょう。キー条件を書けないなら、そのクエリは Scan です。
それから DynoTable を試して、自分のテーブルに対して両方の形を閲覧し、アクセスパターンが実際にどちらを求めているかを見てください。