DynamoDB でシングルテーブル設計を使うべきでないとき
シングルテーブル設計は DynamoDB のデフォルトのアドバイスであり、それに値します: 1回の Query が親とその子を返し、結合も N+1 もありません。
しかしそれはトレードです — 硬直した不透明なスキーマと引き換えに読み取り速度を買います。一部のワークロードはその代償を払えず、それらに1つのテーブルを強いることはそれ自体が罠です。
DynamoDB でシングルテーブル設計を使うべきでないのはどんなときか?
ワークロードが重い OLAP 分析、少数の無関係なエンティティへの単純な CRUD、または独立してスケールや障害が発生するエンティティである場合は、シングルテーブル設計を避けましょう。そのような場合、複数のテーブルの方が読みやすく、コストは変わらず、柔軟性も高まります。シングルテーブル設計が勝るのは、アクセスパターンが既知で、関連していて、高ボリュームのときだけです。
- 重い分析? シングルテーブルにしないこと。オーバーロードされたキーは OLAP に敵対的です — 列指向ストアにエクスポートし、そこで照会しましょう。
- 少数のアクセスパターンを持つ単純な CRUD? エンティティごとに1つのテーブルで問題なく、読みやすく、パフォーマンス上の代償もありません。
- 独立してスケールまたは失敗するエンティティ? 別々のテーブルなら、それぞれを独自にチューニング、課金、影響範囲の制御ができます。
- シングルテーブルがなお勝つのは、パターンが既知で、関連していて、高ボリュームのとき — それがこの設計が作られたケースです。
シングルテーブルが実際に何を犠牲にするか知る
シングルテーブル設計は無料ではありません。コストを読み取りパスから外し、他のすべてに乗せるだけです。可読性と柔軟性で支払います。
PK/SK の背後に5つのエンティティ型を保持するテーブルは、読みにくく、オンボーディングしにくく、変更しにくいです。新しいアクセスパターンが、パーティション内のすべてのアイテム型にまたがるバックフィルを意味し得ます。
だから問いは「シングルテーブルは良いか?」ではありません。「私のアクセスパターンはその硬直さを正当化するか?」です。正当化しないなら、複数のテーブルに手を伸ばしましょう。
分析ワークロードをシングルテーブルにしない
DynamoDB は OLTP のために作られています — 小さく、既知の、ポイントと範囲の読み取り。分析は OLAP です: GROUP BY、大きな集計、データセット全体にわたるアドホックなスライシング。2つは反対方向に引っ張ります。
シングルテーブル設計は OLAP を良くするどころか悪くします。オーバーロードされたキーと混在したエンティティ型は、分析ジョブが何かを合計する前に、まずどのアイテムがどれなのかをほどかなければならないことを意味します — クリーンな列指向スキャンの逆です。
SQL の出身者の反射は、ライブテーブルに対して集計を書くことです。DynamoDB では、それはフルの Scan です — すべてのアイテムを読んで料金を払う、フルボリュームの Scan の罠です。
修正はより賢いキーではありません。別のストアです。AWS 自身のガイダンスは、DynamoDB を S3 にエクスポートし、Athena のようなクエリエンジンで分析を実行することです。
OLTP と OLAP を別々のエンジンに保ちましょう(AWS DynamoDB Developer Guide、S3DataExport.html)。
単純な 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つの影響範囲を共有します。2つのエンティティの書き込みレートや耐久性のニーズが大きく異なるとき、その共有された運命が負債になります。
フリート追跡システムを想像してください。車両はめったに変わりませんが、そのテレメトリは毎秒注ぎ込まれます:
| VehicleId | Make | Model | Region |
|---|---|---|---|
| VEH-204 | Volvo | FH16 | eu-west |
| DeviceTs | VehicleId | SpeedKph | Fuel |
|---|---|---|---|
| 2026-06-23T10:00:01Z | VEH-204 | 88 | 0.61 |
2つのテーブルなら、テレメトリを大量流入向けにプロビジョニングし、車両を小さく安価に保ち、テレメトリだけに TTL を設定し、テレメトリの書き込みの嵐が車両カタログの読み取りをスロットリングするのを止められます。1つのテーブルはそのすべてを結合してしまいます。
2007年の Amazon Dynamo 論文によれば、パーティショニングと可用性は第一級の関心事です — 独立したテーブルは、その両方に対する独立した制御を与えてくれます。
コミットする前に決定をマッピングする
ワークロードを1つのゲートに通しましょう: エンティティは関連していて、パターンは既知で高ボリュームか? そうでなければ、複数のテーブルです。
これをフローとして示すと — 一番上から始めて、最初に一致する分岐を辿ってください:
右下のリーフだけがシングルテーブルに値します。他のすべてのパスは、複数のテーブルのほうがうまく奉仕されます。
シングル vs 複数、正面から
| 要因 | シングルテーブル | 複数テーブル |
|---|---|---|
| 関連する読み取り | 1回の Query、結合なし | クライアント側の結合または追加の往復 |
| 可読性 | 不透明なオーバーロードされたキー | テーブルごとに1エンティティ、自己文書化 |
| 新しいアクセスパターン | しばしばバックフィル | テーブルか GSI を独立して追加 |
| 分析 / OLAP | 敵対的 — 集計の前にほどく | やはりエクスポート、ただしエンティティごとにクリーン |
| 独立したスケーリング | 共有スループット + 影響範囲 | 別々にチューニング、課金、TTL、バックアップ |
| 最適なとき | 既知で関連した高ボリュームのパターン | 無関係、進化中、または分析的 |
落とし穴 + 次のステップ
鏡像の間違いは過剰修正です — 本当に関連した高ボリュームのエンティティをエンティティごとのテーブルに分割し、アプリ内で SQL スタイルの結合を再構築することです。それがシングルテーブルが殺す N+1 の罠です。
ドグマではなく、アクセスパターンが求める形を選びましょう。
リレーションシップをモデル化するときは、正しいインデックス型に頼りましょう — 追加する前に GSI と LSI の比較を参照してください。
複数テーブルのスキーマに対してクエリを書くときは、まず DynamoDB Expression Builder で KeyConditionExpression を描きましょう。
そうすれば、本番に当たる前にフル Scan の形を捉えられます。
それから DynoTable を試して、自分のテーブルに対して両方の形を閲覧し、アクセスパターンが実際にどちらを求めているかを見てください。