DynamoDB のキー条件式
キー条件式 は、Query に渡す KeyConditionExpression です — リクエストのうち
DynamoDB がアイテムを 見つける ために使う唯一の部分です。それ以外のすべて(フィルタ、
プロジェクション)は、読み取りがすでに計測された後に動きます。
DynamoDB のキー条件式とは何ですか?
キー条件式とは、Query に渡す KeyConditionExpression のことで、DynamoDB がどのアイテムを読み取るかを指定します。は等価条件(PK = :v)でなければならず、には =、<、<=、>、>=、BETWEEN、または begins_with のいずれかの範囲演算子を指定できます。フィルタとは異なり、何が読み取られ課金されるかを決めるのはキー条件式です。
- パーティションキーは等価でなければならない。
PK = :vだけで、それ以外はなし — 範囲もbegins_withもINもありません。DynamoDB はそれをハッシュ化して1つの パーティションを特定します。 - ソートキーは範囲演算子を取る。
=、<、<=、>、>=、BETWEEN、またはbegins_with— ここでアイテムコレクションをスライスします。 - それはフィルタではない。 キー条件は何が 読まれ 課金されるかを決め、
FilterExpressionは読み取り分を支払った後に結果を刈り込むだけです。 - ソートキーはバイト順。 範囲演算子は辞書順で比較するため、ソートキー文字列をどう フォーマットするか が あなたのクエリ力です。
なぜパーティションキーは等価に固定されているのか
DynamoDB はパーティションキーを物理パーティションにハッシュ化してアイテムを保存します。 ハッシュは範囲ではなく1つの位置をくれるため、またいで スキャンするものがありません。
だから PK > :v や begins_with(PK, :v) はきっぱり拒否されます。エンジンは「キーが X で
始まるすべてのパーティション」に、テーブル全体を読まずには答えられません — それはまさに
それが避けるために作られた Scan です。
SQL から来ると、これは逆さまに感じます。WHERE id LIKE 'order%' は Postgres では些細
です。DynamoDB ではパーティションキーは検索可能な列ではなくアドレスです。
力が宿るのはソートキー
1つのパーティション内で、アイテムは ソートキーでソートされて 保存されます。その 順序付けこそ範囲演算子が活用するものです — DynamoDB はある位置にシークして前方に読みます。
| 演算子 | 読むもの | 用途 |
|---|---|---|
SK = :v | 厳密な1アイテム | キーで指定した特定の子 |
SK < / <= / > / >= :v | 開いた1スライス | 「この地点より後のすべて」 |
SK BETWEEN :a AND :b | 閉じた範囲(両端含む) | 境界付きの窓 — 日付範囲 |
begins_with(SK, :p) | プレフィックスのスライス | PK 下の種別や階層 |
キーには LIKE も CONTAINS も ENDS_WITH もありません。部分文字列と接尾辞のマッチは
バイト順ではないため、全読み取りを強います — 設計上、API はそれを許しません。それらは
すでに支払った FilterExpression に住みます。
(AWS: Key condition expressions)
具体例:チャットアプリのメッセージ
チャンネルベースのチャットを作っているとします。1つのテーブルを、チャンネルで パーティショニングし、メッセージ時刻でソートします。オリジナルのキースキーマ:
- パーティションキー
ChannelRef—CH#{channelId} - ソートキー
PostedAt— ISO-8601 タイムスタンプ、MSG#2026-06-23T14:05:00Z
MSG# プレフィックスは、メッセージ行をソート可能に保ち、同じチャンネルの下に同居しうる
他の行種別(ピン留めした設定、メンバーシップ)と区別します。
チャンネルの最新メッセージを読み込む。 パーティションキーだけ、新しい順:
KeyConditionExpression: ChannelRef = :ch
ExpressionAttributeValues: { ":ch": "CH#general" }
ScanIndexForward: falseScanIndexForward: false はソート済みコレクションを逆向きに歩きます — クライアント側で
ソートせずに「最新が先頭」を得る安価な方法です。
begins_with で特定の日。 タイムスタンプがソートキーでテキストとして保存されている
ため、日付プレフィックスはきれいなスライスです。
KeyConditionExpression ChannelRef = :ch AND begins_with(PostedAt, :day)
:ch "CH#general"
:day "MSG#2026-06-23"
これは 2026-06-23 のすべてのメッセージを、それだけを読みます — DynamoDB はプレフィックスに シークし、末尾から外れたら止まります。これが機能するのは、プレフィックスがバイトソートされた 文字列の真の左アンカーだからこそです。
BETWEEN で正確な窓。 「14:00 台のメッセージ」には、両端を含む範囲がプレフィックスに
勝ります。
KeyConditionExpression ChannelRef = :ch AND PostedAt BETWEEN :lo AND :hi
:ch "CH#general"
:lo "MSG#2026-06-23T14:00:00Z"
:hi "MSG#2026-06-23T14:59:59Z"
BETWEEN は両端を含むため、端点を意図的に選びましょう — ここでの off-by-one は、端の
メッセージを静かに落とすか二重にします。
これらの式のいずれも、ExpressionAttributeValues マップが埋められた状態で、
DynamoDB Expression Builder で組み立ててコピー
できます — begins_with と BETWEEN の構文を一発で正しくするのに便利です。
DynoTable で見る
実際のチャンネルパーティションに対して同じキー条件を実行し、消費キャパシティがライブで 更新されるのを見てください。コレクション全体ではなくスライスを読んでいることを確認 できます。
罠:キー条件とフィルタの混同
高くつく間違いは、キーの仕事をさせるために FilterExpression に手を伸ばすことです。
KeyConditionExpression: ChannelRef = :ch
FilterExpression: begins_with(PostedAt, :day)これは上の begins_with キー条件と 同等に見え、同じ行を返します — しかしまず
チャンネルパーティション全体 を読み、それからその日の外のすべてを捨てます。全読み取り
分を課金されます。
フィルタは決して読み取りコストを削りません。DynamoDB がアイテムを計測した後に動きます —
フィルタ付きの Scan と同じ落とし穴です。述語をキー条件に
入れられるなら、そこに属します。
修正は上流にあります。アクセスパターンを1つの PK 等価とソートキー範囲として表現できない なら、それはモデリングのサインです。ソートキーを作り直すか、そのパターン用にキーイング したインデックスを追加します — キーの並べ方は GSI と LSI と シングルテーブル設計 を参照してください。
落とし穴と次のステップ
- パーティションキーは常に
=。 範囲は決してなし。パーティションをまたぐ範囲が必要 なら、単一のQueryを超えてしまっています。 - クエリごとにソートキー条件は1つ。 2つのソートキー述語を
ANDできません。BETWEENかbegins_withのどちらかを選び、両方は使えません。 - 予約語にはエイリアスが必要。
TimestampやNameという名前のキーはExpressionAttributeNames(#ts)を使わないと、クエリがエラーになります。 (AWS: reserved words) BETWEENは両端を含む。 両方の端点がマッチします — それに応じて境界を設計しましょう。
Expression Builder でキー条件を下書きし、それから DynoTable を試して 自分のテーブルに対して実行し、実際に消費するキャパシティを 見てください。