中級読了 2 分

DynamoDB のキー条件式

キー条件式 とは、Query に渡す KeyConditionExpression のことです — リクエストの うち、DynamoDB がアイテムを 見つける のに使う唯一の部分です。それ以外のすべて (フィルタ、射影)は、読み取りがすでに計測された後に動きます。

DynamoDB のキー条件式とは?

キー条件式とは、Query 上の KeyConditionExpression で、DynamoDB にどのアイテムを 読むかを伝えるものです。 は等価(PK = :v)でなければ ならず、 は1つの範囲演算子 — =<<=>>=BETWEENbegins_with — を取ります。これは何が読まれ課金されるかを決める点で、フィルタとは 異なります。

  • は等価でなければなりません。 PK = :v だけで、 それ以外はありません — 範囲も、begins_with も、IN もなし。DynamoDB はそれをハッシュ して1つのパーティションを特定します。
  • は範囲演算子を取ります。 =<<=>>=BETWEENbegins_with — ここで をスライス します。
  • これはフィルタではありません。 キー条件は何が 読まれ 課金されるかを決め、 FilterExpression は読み取りに支払った後で結果を絞るだけです。
  • ソートキーはバイト順です。 範囲演算子は辞書順で比較するので、ソートキーの文字列を どう整形するかが そのまま クエリの力になります。

なぜパーティションキーは等価に固定されるのか

DynamoDB は、パーティションキーをハッシュして物理パーティションに割り当ててアイテムを 格納します。ハッシュは範囲ではなく1つの場所を与えるので — またいで スキャンするものが ありません。

だから PK > :vbegins_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 配下の型または階層

キーに LIKECONTAINSENDS_WITH はありません。部分文字列や接尾辞のマッチングは バイト順ではないので、フル読み取りを強いることになります — 設計上、API はそれを許しません。 部分文字列マッチングは FilterExpression 内の contains() で存在します(そこでは読み取りに すでに支払っています)。接尾辞マッチングはサーバー側にはまったく存在しません — キーを 反転して格納するか、クライアント側でフィルタします。 (AWS: キー条件式)

具体例: チャットアプリのメッセージ

チャンネルベースのチャットを作っているとします。1つのテーブルを、チャンネルで パーティション分割し、メッセージ時刻でソートします。元のキースキーマ:

  • パーティションキー ChannelRefCH#{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_withBETWEEN の構文を一発で正しく書くのに便利です。

このビルダーは pk = … AND begins_with(sk, …) のクエリにプリセットされています — 演算子を変えて KeyConditionExpression が更新されるのを見てください。

リクエストを構築
生成されたコード
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#2026-06"
    }
  }
})

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 のどちらかを選び、両方はできません。
  • 予約語にはエイリアスが必要です。 TimestampName という名前のキーは ExpressionAttributeNames#ts)を使わなければならず、さもないとクエリがエラーに なります。(AWS: 予約語)
  • BETWEEN は両端を含みます。 両方の端点がマッチします — それに合わせて境界を設計 しましょう。

キー条件を Expression Builder で下書きし、それから DynoTable を試して、自分のテーブルに対して実行し、各キー条件がどのスライスを 返すかを正確に見てください。

更新日