DynamoDB の複合プライマリキー
複合プライマリキーは2つの属性です: パーティションキーとソートキー。パーティションキーはアイテムがどこに存在するかを決め、ソートキーはそのパーティション内でアイテムを順序付けます。
SQL の出身者なら、これを一意の id 列としてではなく、GROUP BY partition, ORDER BY sort がテーブル自体に焼き込まれたものとして考えてください。
DynamoDB の複合プライマリキーとは?
DynamoDB の複合プライマリキーは、パーティションキーとソートキーという2つの属性を組み合わせたものです。パーティションキーはアイテムがどの物理パーティションに存在するかを決め、ソートキーはそのパーティション内のアイテムを順序付けます。この2つが合わさってアイテムの一意な識別子を形成し、1回の Query で1つのアイテムではなくソート済みの範囲を返せるようにします。
- 2つのパート、2つの仕事。 パーティションキーはアイテムを物理パーティションにルーティングし、ソートキーはそのパーティションキーを共有するすべてのアイテムを順序付けます。
- 一意性はペア。 2つのアイテムは、ソートキーが異なる限り、パーティションキーの値を共有できます — それが1つのパーティションが多数の行を保持できる仕組みです。
- ソートキーがすべての肝。
Scanなしに、1つのアイテムではなく範囲(>=、between、begins_with)をQueryが返せるようにするものです。 - キーはスカラーでなければならない。 パーティションキーとソートキーは文字列、数値、バイナリのみで、マップもリストも不可です(AWS ドキュメント)。
単純なキー vs 複合キー
単純なプライマリキーはパーティションキーだけです。アイテムを一意に識別し、GetItem で読み戻します。それだけです — 範囲読み取りも、「最新の N 件を取得」もありません。
複合キーはソートキーを加えます。そのたった1つの追加が、DynamoDB をハッシュマップではなくデータベースのように感じさせるものです。
| 単純なキー | 複合キー | |
|---|---|---|
| 属性 | パーティションキーのみ | パーティションキー + ソートキー |
| 一意性 | パーティションキーの値 | 値のペア |
| パーティションあたり複数アイテム | 不可 | 可 |
範囲の Query | 不可(GetItem のみ) | 可(begins_with、between、>) |
| 自然な適合 | ID によるルックアップ | 時系列、1対多、履歴 |
センサー読み取り値のテーブルをモデル化する
フィールドセンサーの一群から温度サンプルを収集しているとします。アクセスパターンは「1つのデバイスの読み取り値を、新しい順に、時間枠内で取得する」です。これは教科書的な複合キーです。
デバイス ID をパーティションキーに、読み取り値のタイムスタンプをソートキーに使います:
| deviceId | readingTs | tempC | humidity |
|---|---|---|---|
| DEV#a1b2 | 2026-06-23T08:00:00Z | 21.4 | 48 |
| DEV#a1b2 | 2026-06-23T08:05:00Z | 21.7 | 47 |
| DEV#a1b2 | 2026-06-23T08:10:00Z | 22.1 | 46 |
| DEV#c9d8 | 2026-06-23T08:00:00Z | 19.8 | 55 |
3つの DEV#a1b2 の読み取り値はすべて同じパーティションに着地し、物理的にまとめて保存され、readingTs でソートされます。
AWS はパーティションキーをハッシュ属性、ソートキーを範囲属性と呼びます — ソートキーは、その中をスキャンできる範囲です(AWS ドキュメント)。
各パーティションキーの下でアイテムが1つのアイテムコレクションに集約される様子はこうです:
パーティションキーに対する1回の Query が、そのデバイスのすべての読み取り値を、すでにタイムスタンプ順で読み取ります — クライアント側でのソートも、2回目の往復もありません。
範囲をスキャンせずに照会する
readingTs は ISO-8601 文字列なので、時系列順でソートするのと同じように辞書順でソートされます。だから時間枠の読み取りは、フィルタではなく、キー条件の範囲になります:
Query
deviceId = "DEV#a1b2"
readingTs BETWEEN "2026-06-23T08:00:00Z" AND "2026-06-23T08:10:00Z"
これは KeyConditionExpression です — DynamoDB がデータを返す前に読み取りを絞るので、枠内のアイテム分だけ料金を払います。FilterExpression は読み取りの後に実行され、スキャンしたすべての分を請求します。それがミニチュア版の Scan の罠です。
その式そのものは、プレースホルダと型付けされた値があり、手で書くのは面倒です。DynamoDB Expression Builder で視覚的に構築し、正確な KeyConditionExpression を SDK 呼び出しにコピーしましょう。
ソートキーを意図的に設計する
ソートキーは無料のメタデータではありません — 範囲読み取りの唯一のレバーなので、クエリに合わせて形作りましょう。
- ソート可能なタイムスタンプを使う。 ISO-8601 文字列やゼロ埋めしたエポック数値は正しくソートされます。生のローカライズされた日付はソートされません。
- 1対多のオーバーロードのためにプレフィックスを付ける。
READING#2026-06-23T08:00:00Zのようなソートキーは、1つのパーティションの下にエンティティ型を混在させ、begins_withでスライスできるようにします。それがシングルテーブル設計への入り口です。 - 高カーディナリティの次元をパーティションキーに置く。 センサー ID は数千の値を持つので、書き込みを均等に分散させます。低カーディナリティのパーティションキー(たとえば
region)はホットパーティションを作ります。
複合キーが噛んでくるとき
これは便利さではなくコミットメントです。罠はこうです: パーティションキーを選んでリリースし、それから別のグループ化を必要とするアクセスパターンを発見します — 「フリート全体で30°Cを超えるすべての読み取り値」。
ベーステーブルはそれに答えられません。パーティションキーは固定されています。選択肢は、別のキーを持つグローバルセカンダリインデックスか、再構築です。
キースキーマをコミットする前に、読み取りを列挙しましょう。プライマリキーの変更は ALTER TABLE ではなく、テーブル移行を意味します。
次のステップ
複合キーは、アイテムコレクション、1対多のリレーションシップ、そして最も有用なインデックス設計の基盤です — 次はシングルテーブル設計と GSI と LSI の比較を読んで、それらがどこへ導くかを見てください。
DynamoDB Expression Builder で KeyConditionExpression を描き、DynoTable を試して、実際のパーティションを閲覧し、自分のテーブルに対してソート順が並ぶ様子を観察しましょう。