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回目の往復もありません。
その時間枠のコスト
パーティション内の2 KB の読み取り値10件は、計測上20 KB になります。DynamoDB は読み取りを4 KB ブロック単位で切り上げるので、その時間枠に対する結果整合性のある Query は3読み取りリクエストユニットです(20 KB → 4 KB ブロック5個 × 各0.5 RCU)。同じ時間枠に対する強整合性の読み取りは5ユニットです(ブロックあたり1 RCU)。同じ10行を10回の個別の GetItem 呼び出しで取得すると、最小1ブロックのルールがアイテムごとに適用されるので → 結果整合性で5ユニット、強整合性で10ユニットになります。追加の往復を数える前の話です。
| 読み取りパターン | アイテム | 触れたデータ | EC 読み取りユニット |
|---|---|---|---|
1回の Query、readingTs が開始と終了の間 | 10 | 20 KB | 3 |
完全な複合キーに対する GetItem 10回 | 10 | 20 KB | 5 |
パーティションのみ Query、humidity はアプリでフィルタ | 10 | 20 KB | 3 |
テーブルを Scan、deviceId と時間枠でフィルタ | 全件 | テーブル全体 | テーブルサイズ次第 |
プロトタイピング中は ReturnConsumedCapacity: TOTAL を渡しましょう。秒あたりのリクエスト数が分かれば、料金計算ツールがそのユニット数を月額の項目に変えてくれます。
範囲をスキャンせずに照会する
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)は を作ります。
秒あたり10,000書き込みのストリームで、パーティションキーの異なる値が50個しかないと、DynamoDB が分割するまで論理パーティションあたり約200 WCU が集中します — プロトタイプ規模なら問題ありませんが、本番規模では苦しくなります。境界の定まったデータセットを意図的に同居させる場合を除き、粗いグループ分けよりも、フリートとともに増えていく識別子(デバイス ID、テナント ID、セッション ID)を選びましょう。
複合キーが噛んでくるとき
これは便利さではなくコミットメントです。罠はこうです: パーティションキーを選んでリリースし、それから別のグループ化を必要とするアクセスパターンを発見します — 「フリート全体で30°Cを超えるすべての読み取り値」。
ベーステーブルはそれに答えられません。パーティションキーは固定されています。選択肢は、別のキーを持つグローバルセカンダリインデックスか、再構築です。
キースキーマをコミットする前に、読み取りを列挙しましょう。プライマリキーの変更は ALTER TABLE ではなく、テーブル移行を意味します。
DynoTable では、テーブルブラウザを開いて複合キーのテーブルとその GSI を並べて確認できます — ソートキー上の並び順が1行ずつ見えるので、タイムスタンプ形式のずれが本番に届く前にはっきり分かります。
次のステップ
複合キーは、アイテムコレクション、1対多のリレーションシップ、そして最も有用なインデックス設計の基盤です — 次はシングルテーブル設計と GSI と LSI の比較を読んで、それらがどこへ導くかを見てください。
DynamoDB Expression Builder で KeyConditionExpression を描き、クエリビルダーでページネーション込みの完全な Query を出力し、それからDynoTable を試して、実際のパーティションを閲覧し、自分のテーブルに対してソート順が並ぶ様子を観察しましょう。