初級読了 2 分

例で学ぶ DynamoDB の PartiQL

は DynamoDB 向けの SQL 互換クエリ言語です。アドホックな作業では生の API より 親しみやすいものの、同じエンジン上で動作するため、おなじみの構文の下で同じキーのルール(そして 同じコスト)が適用されます。

DynamoDB 向けの PartiQL クエリはどう書くのか?

PartiQL は DynamoDB に 4 つの SQL 形式のステートメント — SELECTINSERTUPDATEDELETE — を、 ExecuteStatement を通して提供します。それぞれが下層でネイティブ操作にコンパイルされるため、 でフィルタすれば Query のままですが、それを省くとテーブル全体の Scan になります。書き込みは依然としてで 1 アイテムをターゲットにします。 リレーショナルな JOINGROUP BY、集計はありません。

どのステートメントも同じ ExecuteStatement API(またはそのバッチ/トランザクション版)を通ります。 テーブル名とインデックス名は、特殊文字を含む場合はダブルクォートで囲む必要があります。式の中の 属性名は、低レベル API と同じ引用ルールに従います。DynoTable の PartiQL ドキュメントは、 スキーマを認識した自動補完と、過去の実行を検索できる履歴とともに、ここでの例を再現しています。

SELECT

SELECT * FROM "AppData"
WHERE "PK" = 'CUSTOMER#42' AND begins_with("SK", 'ORDER#')

でフィルタすれば、これは Query です。パーティションキーを省くと PartiQL は暗黙のうちにテーブル全体の Scan を実行します — 同じ落とし穴が、 SELECT * の裏に隠れているだけです。

WHERE 句の形コンパイル後の操作主なコスト要因
PK = ?(パーティション完全一致)Queryそのパーティション内のアイテム(+フィルターの無駄)
PK = ? AND begins_with(SK, ?)Query1 パーティション内のソートキー範囲
FROM "Table"."GSI1" 経由の GSI1PK = ?インデックスへの Queryインデックスのパーティション内のアイテム
テーブルにもインデックスにもパーティションキー等価なしScanフィルター前の、対象内のすべてのアイテム

Scan 側に数字を入れてみましょう。平均 1 KB のアイテムが 50 万件あるテーブルでは、SELECT * の 全走査はオンデマンド課金でおよそ 62,500 読み取りリクエストユニットを計測します(500,000 KB ÷ 1 ユニットあたり 4 KB、結果整合性で 1 件あたり 0.5 RCU)。WHERE status = 'OPEN' を足しても請求額は 同じです — フィルターは、読み取りが計測されたあとに効きます。上の例のキーを使った SELECT が 触れるのは CUSTOMER#42 のパーティションだけで、そのコレクションが 1 KB の注文を 40 件持って いるなら、同じ結果整合性の読み取りは 62,500 ではなく 5 ユニットです。

ExecuteStatement はネイティブ API と同じようにページングします。NextToken が空でなければ、 まだ行が残っています。トークンが返らなくなるまでループしてください(ページネーションのガイド)。

INSERT

INSERT INTO "AppData" VALUE {'PK': 'CUSTOMER#42', 'SK': 'PROFILE', 'plan': 'pro'}

UPDATE

UPDATE "AppData" SET "plan" = 'enterprise'
WHERE "PK" = 'CUSTOMER#42' AND "SK" = 'PROFILE'

DELETE

DELETE FROM "AppData"
WHERE "PK" = 'CUSTOMER#42' AND "SK" = 'ORDER#2026-001'

インデックスをクエリする

FROM 句でインデックス名を使います。

SELECT * FROM "AppData"."GSI1" WHERE "GSI1PK" = 'STATUS#OPEN'

WHEREINcontains()begins_with() もサポートします。

SELECT * FROM "AppData"
WHERE "PK" = 'CUSTOMER#42' AND "SK" IN ['ORDER#1', 'ORDER#2']

パラメータ化されたステートメント

値をインラインで埋め込む代わりに ? プレースホルダーを使います — 引用符付けやインジェクションの 問題を回避でき、SDK に型のマーシャリングを任せられます。

SELECT * FROM "AppData" WHERE "PK" = ? AND begins_with("SK", ?)

Parameters: [{ S: 'CUSTOMER#42' }, { S: 'ORDER#' }]ExecuteStatement に渡します。

アプリケーションのコードではパラメータを優先し、文字列の埋め込みはアドホックなコンソール操作に とどめましょう。SDK が型(数値なら N、真偽値なら BOOL)をマーシャリングしてくれるので、 DynamoDB が数値属性を期待しているところに "42" という文字列を渡す、あの典型的な失敗を避けられます。 予約語の属性名は、FilterExpression とまったく同じように PartiQL でもエイリアスが必要です — 保存する ステートメントに焼き込む前に、予約語チェッカーに名前を 通しておきましょう。

バッチとトランザクション

  • BatchExecuteStatement — 1 回の往復で最大 25 ステートメント。より高速ですが、アイテム間の アトミック性はありません(各ステートメントが個別に成功または失敗します)。
  • ExecuteTransaction — 最大 100 ステートメント、オールオアナッシング。複数の書き込みを まとめてコミットしなければならないときに使います。

バッチとトランザクションの上限は、チューニング用のつまみではなく固定のサービス制限です。 30 明細に触れるカートの決済は、やはり分割が必要になります。BatchExecuteStatement を 2 回 (25 + 5)呼ぶか、30 件すべてにまたがるアトミック性が必要ならトランザクションを 1 回使います (それでも各ステートメントは、完全なプライマリキーで単一のアイテムを対象にします)。

PartiQL と Workbench SQL

PartiQL は DynamoDB 上で 実行されます — 1 ステートメントにつきキーを使ったアクセスパターン 1 つです。DynoTable の SQL Workbench は、すでに取得した行に対して クライアント側で 実行されるため、PartiQL があえて省いている JOINGROUP BY、集計が使えます。 この住み分けは見た目の話ではなく、運用上のものです。

必要なこと使うもの
プライマリキーによる単一パーティションの読み書きPartiQL の SELECT / DML
GSI を設計しておいたパーティション横断のフィルターインデックスに対する PartiQL
2 つのエンティティ型をまたぐアドホックな結合範囲を絞ったクエリの上での Workbench SQL
SKU 別の月次売上の集計Workbench の GROUP BY
本番のホットパスにあるマイクロサービスネイティブの Query / GetItem API

Workbench も DynamoDB のアクセスルールを守ります。パーティションはキーを使った読み取り(または 意識して受け入れた範囲限定の Scan)で取得し、そのうえで SQL が結果セットをローカルで整形します。 詳しい比較は PartiQL と SQL を参照してください。

PartiQL の落とし穴

PartiQL は SQL に_見えます_が DynamoDB のエンジン上で動作するため、SQL の習慣が裏目に出ます。

  • 単一の UPDATE/DELETE は完全な1 つのアイテムをターゲットに しなければなりません — UPDATE … WHERE status = 'x' のような一括更新はありません(代わりにバッチで ループします)。
  • JOINGROUP BY も集計(COUNT/SUM/AVG)もありません。 PartiQL vs SQL を参照してください。
  • パーティションキーを省くと、任意の SELECT がテーブル全体の Scan になります — 制限は請求額だけです。

本当に JOINGROUP BY、集計が必要になったとき、DynoTable の SQL Workbench は、あなたが取得した 行に対してクライアント側でそれらを実行します — PartiQL には話せない SQL を、DynamoDB のアクセス パターンのルール内で。

PartiQL は下層のデータ型を変えません — 値は依然として DynamoDB-JSON として ワイヤ上を送られ、コンバータで確認できます。

UPDATE と DELETE のステートメントでは、WHERE 句がちょうど 1 件のアイテムに解決しなければ なりません。多数の行に一致する部分キーの WHERE 句は、一括更新になるのではなくコンパイル時か 実行時に失敗します — キーを使った Query でループし、アトミック性が必要なら BatchExecuteStatement かトランザクションの中でアイテムごとに DML を発行してください。

複雑な KeyConditionExpression の形(入れ子の begins_withANDOR の混在)は、 DynamoDB Expression Builderで試作し、条件が正しくなってから PartiQL の WHERE 構文に置き換えるほうが簡単です。

DynoTable を試すと、スキーマを認識した自動補完で PartiQL ステートメントを実行し、 ソート可能なテーブルビューで結果を閲覧できます — エンジンがプッシュダウンできない結合や集計が 必要になったら、同じデータセットをそのまま Workbench に持ち込めます。

更新日