中級読了 3 分

DynamoDB ソートキー戦略:3 つのパターンと使い分け

DynamoDB のプライマリキーは1つまたは2つの属性です。 単独か、 パーティションキーとソートキーです。パーティションキーは、どの物理パーティションがアイテムを保持するかを 決めます。

ソートキーは、そのパーティション内のアイテムの順序を決めます — そして、その順序付けこそが Query を 強力にします。

間違ったソートキーを選ぶと、データを書き込むことはできても、範囲読み取り、順序付け、そして1つのコレクションからの いくつかのアクセスパターンを失います。

SQL から来ると、後付けで ORDER BY やセカンダリインデックスに手を伸ばすでしょう。DynamoDB では、順序を 前もってキーに焼き込みます。さもなければ手に入りません。

DynamoDB のソートキーはどのように機能するのか?

DynamoDB のソートキーはパーティション内のアイテムを順序付けるので、Query は1件ずつアイテムを取得する 代わりに範囲読み取り — >=betweenbegins_with — を行えます。文字列のソートキーは UTF-8 バイトで 順序付けられます(数値は数値として順序付けられます)。そのため、バイト順が読みたい順序と等しくなるように 文字列キー(ISO-8601 のタイムスタンプ、ゼロ埋めした数値)を設計します。

  • ソートキーはパーティション内のインデックス。 ディスク上で を順序付けるので、 Query は単一の GetItem の代わりに範囲読み取り(>=betweenbegins_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 型を 使い、429 より前ではなく後にソートされるようにします。

数値を複合文字列のソートキーの中に置かなければならない場合は、固定幅にゼロ埋めします。

戦略 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 はアイテムを昇順のソートキー順で格納し、デフォルトではその順で読みます。新しい順に読むには — アクティビティフィードにとって自然な順序 — QueryScanIndexForward = false を設定します。

これは読み取り時のフラグであって、スキーマ上の決定ではありません。同じコレクションが同じコストで両方向に 応えます。降順の読み取りを得るためだけに、タイムスタンプを反転(「逆エポック」を保存)してはいけません。

1つのアイテムコレクションを、昇順で一度だけ格納し、どちらの向きにも読みます。

ScanIndexForward = trueScanIndexForward = falseアイテムコレクション(1つの PK)SK EVT#09:00SK EVT#14:00SK EVT#next-day古い順新しい順

同じアイテム、同じパーティション、同じコスト — 異なるのは読み取り方向だけです。

実例: アクターにスコープされた監査ログ

SaaS 製品で、アクター — ユーザー、サービス、API キー — が生成したタイムスタンプ付きのイベントを記録し、 2つの読み取りがあるとしましょう。

  1. 1人のアクターのアクティビティストリーム、新しいイベントから順に。
  2. 1人のアクターの、ある時間枠内のイベント(例: 「2つのデプロイの間のすべて」)、調査のため。

どちらの読み取りも単一のアクターにスコープされているので、アクターがパーティションキー、イベント時刻が ソートキーです。同じテーブルが後で他のエンティティを保持できるよう、汎用的なキー名を使います。

PKSKattributes
ACTOR#u_8814EVT#2026-06-23T09:12:04Zaction=login, ip, ua
ACTOR#u_8814EVT#2026-06-23T14:05:11Zaction=export, target
ACTOR#u_8814EVT#2026-06-24T08:40:55Zaction=login, ip, ua
ACTOR#svc_billingEVT#2026-06-23T00:00:00Zaction=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_withbetween の ソートキー条件について、KeyConditionExpressionExpressionAttributeNamesExpressionAttributeValues を生成します。

実行時にエスケープをデバッグする代わりに、それをそのまま SDK 呼び出しにコピーしましょう。

DynoTable でやってみる

ソートキーの設計は反復的です。いくつかの代表的なアイテムを書き込み、範囲クエリを実行して、行が期待した順序で 返ってくるかを確認します。それを GUI で稼働中のテーブルに対して行うほうが、コード越しに往復するより優れています。

DynoTable で、ソートキーの between 条件を使ってアクターの監査ログコレクションをクエリし、結果を新しい順に並べた様子。
DynoTable で、ソートキーの between 条件を使ってアクターの監査ログコレクションをクエリし、結果を新しい順に並べた様子。

ソート方向を切り替え、between の境界を絞り込み、1行もコードを書かずに返されるコレクションが変わる様子を 眺めましょう — コミットする前にソートキー設計を確認する最速の方法です。

落とし穴と次のステップ

  • ソートキーはパーティション内で一意でなければならない。 2つのイベントがタイムスタンプを共有しうるなら、 複合キーが一意のままになるよう、ソートキーに識別子(シーケンス番号や短い ID)を追加します。
  • ホットパーティションはソートでは回避できない。 1人のアクターが他より圧倒的に多くのイベントを生成するなら、 ソートキーは救ってくれません — 負荷を分散するパーティションキー設計が必要です。 シングルテーブル設計を参照してください。
  • 2つ目のソート順には2つ目のインデックスが必要。 ベーステーブルのソートキーは1つの順序付けを与えます。 同じアイテムを別の順(たとえばイベントタイプ順)に並べるには、別のソートキーを持つ GSI を追加します — ローカル対グローバルのセカンダリインデックスのトレードオフを比較しながら。
  • 「後でソートする」ために Scan に手を伸ばさない。 Scan の後にクライアント側でソートするのは、 テーブル全体を読み取って順序付けを捨てることです。それが Scan の落とし穴です。 代わりに順序をソートキーに押し込みましょう。

キー条件が正しくなったら、DynoTable を試して コレクションをモデリングし、昇順と降順のクエリを 並べて実行し、リリース前に実データに対してソートキー戦略を検証しましょう。

更新日