DynamoDB のキー条件式
キー条件式 とは、Query に渡す KeyConditionExpression のことです — リクエストの
うち、DynamoDB がアイテムを 見つける のに使う唯一の部分です。それ以外のすべて
(フィルタ、射影)は、読み取りがすでに計測された後に動きます。
DynamoDB のキー条件式とは?
キー条件式とは、Query 上の KeyConditionExpression で、DynamoDB にどのアイテムを
読むかを伝えるものです。 は等価(PK = :v)でなければ
ならず、 は1つの範囲演算子 — =、<、<=、>、>=、BETWEEN、
begins_with — を取ります。これは何が読まれ課金されるかを決める点で、フィルタとは
異なります。
- は等価でなければなりません。
PK = :vだけで、 それ以外はありません — 範囲も、begins_withも、INもなし。DynamoDB はそれをハッシュ して1つのパーティションを特定します。 - は範囲演算子を取ります。
=、<、<=、>、>=、BETWEEN、begins_with— ここで をスライス します。 - これはフィルタではありません。 キー条件は何が 読まれ 課金されるかを決め、
FilterExpressionは読み取りに支払った後で結果を絞るだけです。 - ソートキーはバイト順です。 範囲演算子は辞書順で比較するので、ソートキーの文字列を どう整形するかが そのまま クエリの力になります。
なぜパーティションキーは等価に固定されるのか
DynamoDB は、パーティションキーをハッシュして物理パーティションに割り当ててアイテムを 格納します。ハッシュは範囲ではなく1つの場所を与えるので — またいで スキャンするものが ありません。
だから PK > :v や begins_with(PK, :v) は端から拒否されます。エンジンは、テーブル全体を
読まずに「キーが X で始まるすべてのパーティション」に答えられません。それはまさに、
DynamoDB が避けるために作られた 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 内の contains() で存在します(そこでは読み取りに
すでに支払っています)。接尾辞マッチングはサーバー側にはまったく存在しません — キーを
反転して格納するか、クライアント側でフィルタします。
(AWS: キー条件式)
具体例: チャットアプリのメッセージ
チャンネルベースのチャットを作っているとします。1つのテーブルを、チャンネルで パーティション分割し、メッセージ時刻でソートします。元のキースキーマ:
- パーティションキー
ChannelRef—CH#{channelId} - ソートキー
PostedAt— ISO-8601 のタイムスタンプ、MSG#2026-06-23T14:05:00Z
MSG# プレフィックスは、メッセージ行をソート可能に保ち、同じチャンネル配下に同居させうる
他の行タイプ(ピン留め設定、メンバーシップ)と区別します。
チャンネルの最新メッセージを読み込む。 パーティションキーだけで、新しい順に:
KeyConditionExpression ChannelRef = :ch
ExpressionAttributeValues { ":ch": "CH#general" }
ScanIndexForward false
ScanIndexForward: false は、ソート済みのコレクションを逆向きに歩きます — クライアント
側でソートせずに「最新が先頭」を得る安上がりな方法です。
begins_with で特定の1日を。 タイムスタンプがソートキーであり、テキストとして
格納されるので、日付プレフィックスはきれいなスライスになります。
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 の構文を一発で正しく書くのに便利です。
このビルダーは pk = … AND begins_with(sk, …) のクエリにプリセットされています —
演算子を変えて KeyConditionExpression が更新されるのを見てください。
DynoTable で見る
実際のチャンネルのパーティションに対して、同じキー条件を実行してみてください。パーティション
キーのフィルタを設定した瞬間、DynoTable は Query を発行します — なので、コレクション全体
ではなく、そのスライスだけを読み込みます。
罠: キー条件とフィルタを混同する
高くつく間違いは、キーの仕事をさせようと FilterExpression に手を伸ばすことです。フィルタは
PostedAt を参照することすらできません — それはソートキーであり、DynamoDB はキー属性への
フィルタを ValidationException で拒否します。そこで回避策は、日付を素の非キー属性
(MessageDate)に複製し、代わりにそれをフィルタすることです。
KeyConditionExpression ChannelRef = :ch
FilterExpression begins_with(MessageDate, :day)
これは上記の begins_with キー条件と 同等に見え、同じ行を返します — が、まず
チャンネルのパーティション全体 を読み、それからその日以外をすべて捨てます。フル
読み取り分を課金されます。
フィルタは読み取りコストを決して減らしません。DynamoDB がアイテムを計測した後に動くのは、
フィルタ付き Scan と同じ自爆装置です。述語をキー条件に入れられる
なら、そこに属します。
対処は上流にあります。あるアクセスパターンを、1つの PK 等価とソートキーの範囲として 表現できないなら、それはモデリングのサインです。ソートキーを作り直すか、そのパターン用に キー付けしたインデックスを追加します — キーの並べ方は GSI と LSI と シングルテーブル設計 を参照してください。
落とし穴と次のステップ
- パーティションキーは常に
=。 範囲は、決してありません。パーティションをまたぐ範囲が 必要なら、単一のQueryの域を超えています。 - クエリごとにソートキー条件は1つ。 2つのソートキー述語を
ANDすることはできません。BETWEENかbegins_withのどちらかを選び、両方はできません。 - 予約語にはエイリアスが必要です。
TimestampやNameという名前のキーはExpressionAttributeNames(#ts)を使わなければならず、さもないとクエリがエラーに なります。(AWS: 予約語) BETWEENは両端を含みます。 両方の端点がマッチします — それに合わせて境界を設計 しましょう。
キー条件を Expression Builder で下書きし、それから DynoTable を試して、自分のテーブルに対して実行し、各キー条件がどのスライスを 返すかを正確に見てください。