DynamoDB ソートキー戦略:3 つのパターンと使い分け
DynamoDB のプライマリキーは1つまたは2つの属性です。 単独か、 パーティションキーとソートキーです。パーティションキーは、どの物理パーティションがアイテムを保持するかを 決めます。
ソートキーは、そのパーティション内のアイテムの順序を決めます — そして、その順序付けこそが Query を
強力にします。
間違ったソートキーを選ぶと、データを書き込むことはできても、範囲読み取り、順序付け、そして1つのコレクションからの いくつかのアクセスパターンを失います。
SQL から来ると、後付けで ORDER BY やセカンダリインデックスに手を伸ばすでしょう。DynamoDB では、順序を
前もってキーに焼き込みます。さもなければ手に入りません。
DynamoDB のソートキーはどのように機能するのか?
DynamoDB のソートキーはパーティション内のアイテムを順序付けるので、Query は1件ずつアイテムを取得する
代わりに範囲読み取り — >=、between、begins_with — を行えます。文字列のソートキーは UTF-8 バイトで
順序付けられます(数値は数値として順序付けられます)。そのため、バイト順が読みたい順序と等しくなるように
文字列キー(ISO-8601 のタイムスタンプ、ゼロ埋めした数値)を設計します。
- ソートキーはパーティション内のインデックス。 ディスク上で を順序付けるので、
Queryは単一のGetItemの代わりに範囲読み取り(>=、between、begins_with)を行えます。 - 文字列のソートキーは UTF-8 バイトで順序付ける(数値は数値として順序付ける)。 バイト順が読みたい順序と
等しくなるように文字列キーを設計します — ISO-8601 のタイムスタンプ、ゼロ埋めした数値であって、生の UUID や
6/23/2026は決して使いません。 - 形の整った1つのソートキーが多くのアクセスパターンに応える。 (
EVT#<timestamp>)は、 プレフィックスであると同時に範囲でもあります — GSI は不要です。 - 方向は無料。
ScanIndexForward = falseは同じコストで新しい順に読みます。それを偽装するために逆順の タイムスタンプを保存してはいけません。
なぜソートキーがてこなのか
ソートキーがないと、パーティション内のすべてのアイテムは完全なプライマリキーによってのみアドレス指定可能です —
せいぜい GetItem です。ソートキーを追加すると、DynamoDB はアイテムをパーティション内でそれによって
ソートして格納し、それが Query を解き放ちます。
つまり、範囲条件(>=、between)、プレフィックスの一致(begins_with)、そして昇順または降順で読むための
ScanIndexForward フラグです。
AWS DynamoDB デベロッパーガイドによれば、あるパーティションキーを共有するすべてのアイテムは アイテムコレクションを形成し、ディスク上でソートキーによって順序付けられます。
ですからソートキーは単なる2つ目の識別子ではありません。それはパーティション内でクエリの対象となるインデックスです。
その順序付けは、エンコードされたソートキーのバイト順です。文字列は UTF-8 バイトで比較され、数値は数値として 比較されます。この1つの事実が、以下のほぼすべての戦略を駆動します。
範囲クエリに意味を持たせたいなら、バイト順が読みたい順序と一致していなければなりません。
戦略 1: ソートキーをソート可能にする
最も一般的な間違いは、意味のある順序付けがされていないソートキーです。ランダムな UUID は一意性を与えますが、 有用な範囲クエリは与えません — 「最後の20件をくれ」は、バイト順が恣意的なので不可能になります。
代わりに、ソートしフィルターする値を、バイト順が論理順と等しくなる表現で、ソートキーの中にエンコードします。 タイムスタンプなら、それは辞書順でソート可能な形式を意味します。ISO-8601 の文字列か、ゼロ埋めしたエポックです。
ISO-8601 は、文字列比較が時系列比較と等しくなるように設計されました — まさに範囲クエリが必要とするものです。
6/23/2026 のような形式は避けましょう。月が繰り上がった瞬間に、間違った順にソートされます。
数値(バージョンカウンター、スコア)でソートするなら、文字列ではなく DynamoDB のネイティブな Number 型を
使い、42 が 9 より前ではなく後にソートされるようにします。
数値を複合文字列のソートキーの中に置かなければならない場合は、固定幅にゼロ埋めします。
戦略 2: 階層のための複合ソートキー
ソートキーは、区切り文字 — 最も一般的には # — でセグメントを連結することで階層をエンコードできます。
すると1つの begins_with 条件がサブツリー全体を選択します。
| SK |
|---|
| EVENT#2026-06#01#login |
| EVENT#2026-06#03#export |
| EVENT#2026-07#02#login |
begins_with(SK, "EVENT#2026-06#") は6月のイベントだけを返します。より広い
begins_with(SK, "EVENT#") はそのすべてを返します。
セグメントの順序は設計上の決定です。粗いものから細かいものへ(年 → 月 → 日)並べると、関連するアイテムが 連続して保たれるので、範囲読み取りは、パーティション全体への散らばりではなく、1回の安価なクエリのままです。
戦略 3: ScanIndexForward で方向を制御する
DynamoDB はアイテムを昇順のソートキー順で格納し、デフォルトではその順で読みます。新しい順に読むには —
アクティビティフィードにとって自然な順序 — Query に ScanIndexForward = false を設定します。
これは読み取り時のフラグであって、スキーマ上の決定ではありません。同じコレクションが同じコストで両方向に 応えます。降順の読み取りを得るためだけに、タイムスタンプを反転(「逆エポック」を保存)してはいけません。
1つのアイテムコレクションを、昇順で一度だけ格納し、どちらの向きにも読みます。
同じアイテム、同じパーティション、同じコスト — 異なるのは読み取り方向だけです。
実例: アクターにスコープされた監査ログ
SaaS 製品で、アクター — ユーザー、サービス、API キー — が生成したタイムスタンプ付きのイベントを記録し、 2つの読み取りがあるとしましょう。
- 1人のアクターのアクティビティストリーム、新しいイベントから順に。
- 1人のアクターの、ある時間枠内のイベント(例: 「2つのデプロイの間のすべて」)、調査のため。
どちらの読み取りも単一のアクターにスコープされているので、アクターがパーティションキー、イベント時刻が ソートキーです。同じテーブルが後で他のエンティティを保持できるよう、汎用的なキー名を使います。
| PK | SK | attributes |
|---|---|---|
| ACTOR#u_8814 | EVT#2026-06-23T09:12:04Z | action=login, ip, ua |
| ACTOR#u_8814 | EVT#2026-06-23T14:05:11Z | action=export, target |
| ACTOR#u_8814 | EVT#2026-06-24T08:40:55Z | action=login, ip, ua |
| ACTOR#svc_billing | EVT#2026-06-23T00:00:00Z | action=invoice.run |
EVT# プレフィックスと ISO-8601 のタイムスタンプが、ソート可能なソートキーを与えます。読み取り1は、
新しい順のための ScanIndexForward = false を付けた Query PK = "ACTOR#u_8814" です。読み取り2は、
ソートキーの between 条件で同じパーティションを絞り込みます。
Query
PK = "ACTOR#u_8814"
AND SK BETWEEN "EVT#2026-06-23T00:00:00Z"
AND "EVT#2026-06-23T23:59:59Z"
1つのコレクション、2つのアクセスパターン、GSI なし — ソートキーがプレフィックス(EVT#)であると同時に
範囲(タイムスタンプ)でもあるからです。降順の読み取りと時間枠の読み取りは、同じ順序の同じアイテムです。
異なるのはパラメーターだけです。
そのキー条件を手作業で組み立てると、between の境界や、属性名の予約語エスケープを間違えやすくなります。
DynamoDB 式ビルダーは、begins_with や between の
ソートキー条件について、KeyConditionExpression、ExpressionAttributeNames、ExpressionAttributeValues
を生成します。
実行時にエスケープをデバッグする代わりに、それをそのまま SDK 呼び出しにコピーしましょう。
DynoTable でやってみる
ソートキーの設計は反復的です。いくつかの代表的なアイテムを書き込み、範囲クエリを実行して、行が期待した順序で 返ってくるかを確認します。それを GUI で稼働中のテーブルに対して行うほうが、コード越しに往復するより優れています。

ソート方向を切り替え、between の境界を絞り込み、1行もコードを書かずに返されるコレクションが変わる様子を
眺めましょう — コミットする前にソートキー設計を確認する最速の方法です。
落とし穴と次のステップ
- ソートキーはパーティション内で一意でなければならない。 2つのイベントがタイムスタンプを共有しうるなら、 複合キーが一意のままになるよう、ソートキーに識別子(シーケンス番号や短い ID)を追加します。
- ホットパーティションはソートでは回避できない。 1人のアクターが他より圧倒的に多くのイベントを生成するなら、 ソートキーは救ってくれません — 負荷を分散するパーティションキー設計が必要です。 シングルテーブル設計を参照してください。
- 2つ目のソート順には2つ目のインデックスが必要。 ベーステーブルのソートキーは1つの順序付けを与えます。 同じアイテムを別の順(たとえばイベントタイプ順)に並べるには、別のソートキーを持つ GSI を追加します — ローカル対グローバルのセカンダリインデックスのトレードオフを比較しながら。
- 「後でソートする」ために
Scanに手を伸ばさない。Scanの後にクライアント側でソートするのは、 テーブル全体を読み取って順序付けを捨てることです。それが Scan の落とし穴です。 代わりに順序をソートキーに押し込みましょう。
キー条件が正しくなったら、DynoTable を試して コレクションをモデリングし、昇順と降順のクエリを 並べて実行し、リリース前に実データに対してソートキー戦略を検証しましょう。


