DynamoDB の Update Expression:SET・REMOVE・ADD・DELETE(例付き)
更新式は UpdateItem に、1 つの項目をどう変更するかを伝えます。どのを書き込み、インクリメントし、削除し、あるいはセットに畳み込むか、です。複数行にわたる UPDATE … SET … WHERE はありません — 完全なキーで単一の項目を名指しし、4 つの句キーワードで変更を記述します。
DynamoDB の更新式はどう動きますか?
DynamoDB の更新式は UpdateItem に、4 つの句を使って 1 つの項目をどう変更するかを伝えます。SET はを書き込むか上書きします。ADD は数値をアトミックにインクリメントするか、セットに和集合として加えます。REMOVE は属性、またはリスト要素を 1 つ削除します。DELETE はセットから特定のメンバーを取り除きます。1 回の呼び出しで 4 つすべてを同時に運べます。
SETは属性を書き込むか上書きします — スカラー、ドキュメント、そして関数イディオムのif_not_existsとlist_appendです。ADDは 1 回のラウンドトリップで、事前読み取りなしに、アトミックな数値インクリメントかセットの和集合を行います。REMOVEは属性をまるごと削除します(またはインデックスでリスト要素を 1 つ削除します)。DELETEはセットから特定のメンバーを取り除きます — セットからのみです。
SQL から来ると、罠は何にでも SET に手を伸ばすことです。ADD と DELETE が存在するのは、カウンターやセットに対する read-modify-write が、並行実行下では負ける競合だからです。
変更する対象で句を選ぶ
1 回の UpdateItem 呼び出しで、4 つの句すべてを任意の順序で同時に運べます。各キーワードは最大 1 回だけ現れ、カンマ区切りのアクションのリストを取ります。
| 句 | 対象 | 用途 |
|---|---|---|
SET | 任意の属性 | 値やドキュメントフィールドを書き込む/上書きする |
ADD | 数値またはセットのみ | アトミックにインクリメントする、またはセットに和集合として加える |
REMOVE | 任意の属性またはリスト要素 | 属性を削除する。リストインデックスを 1 つ落とす |
DELETE | セットのみ | セットから特定のメンバーを取り除く |
文字列への ADD やスカラーへの DELETE は no-op ではなく検証エラーです — DynamoDB は呼び出し全体を拒否します。AWS の更新式リファレンスによれば、ADD は数値とセットに、DELETE はセットに限定されます。
実例: ショッピングカート
カート 1 つにつき項目 1 つ、CartPK = "CART#c-9f21" と CartSK = "SUMMARY" でキー付けします。稼働中の OrderTotal、LineItems リスト、PromoCodes 文字列セット、そして ItemCount を追跡します。
SET — スカラーとドキュメントを書き込む
SET はそこにあったものを上書きします。同じ呼び出しで、ライン項目をリストに追加し、合計を増やします。
SET OrderTotal = :total,
LineItems = list_append(LineItems, :newItem),
UpdatedAt = :now
list_append(LineItems, :newItem) は末尾に追加します。引数を入れ替えて list_append(:newItem, LineItems) にすると先頭に追加します。引数の順序が連結の順序であり、それ以上の意味はありません。
その最初の呼び出しには落とし穴があります。カートが新品なら LineItems はまだ存在せず、存在しない属性への list_append は失敗します。if_not_exists でガードします。
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem)
if_not_exists(LineItems, :empty) は、存在すれば現在のリストを、なければフォールバックの :empty(空リスト [])を返します。これにより、最初の追加もその後のすべての追加も同じ式を使えます — これらのイディオムが存在する本当の理由です。
ADD — カウントをアトミックにインクリメントする
ItemCount を増やすには、それを読み取り、コードで 1 を足して SET で書き戻すことは しないでください。それは更新消失の競合です。2 つの同時の追加が両方とも 3 を読み、両方とも 4 を書き、1 つを落とします。ADD は算術をサーバー側で行います。
ADD ItemCount :one
:one = 1 なら、これはアトミックカウンターです。同時呼び出しは項目上で直列化されるので、2 つの追加は +2 として着地します。負の数を渡すとデクリメントします。ItemCount が存在しなければ、ADD はまず 0 として扱う — なのでカウンターをシードする必要はありません。
us-east-1 のオンデマンドでは、ADD は書き込み後のアイテムについて 1 KB あたり 1 WCU(切り上げ)を課金します。ADD ItemCount :one を 1 つ持つ 1 KB のカート要約は 1 WCU です — 同じ行を read-modify-write の SET で書いても WCU は同じですが、並行実行下では負けます。アイテムのサイズはアイテムサイズ計算ツールで測りましょう。
このとおりの式 — 名前、型付き値、そしてマーシャリングされたリクエスト — を、1 つの #name や :value プレースホルダーも手でエスケープすることなく、DynamoDB 式ビルダーで構築できます。
下のプリセット — SET とアトミックな ADD — を編集して、UpdateExpression が入力に合わせて再構築されるのを見てください。
REMOVE — 属性やライン項目を 1 つ落とす
REMOVE は属性をまるごと削除する方法です(「null にセット」はありません — それは単に NULL 型を書き込むだけです)。適用済みの割引をクリアし、3 番目のライン項目を 1 回の呼び出しで落とします。
REMOVE AppliedDiscount, LineItems[2]
LineItems[2] はインデックス 2 の要素を削除し、その後のすべてを 1 つ下へシフト します — インデックス 3 が 2 になり、以下同様です。1 つの式で 2 つのインデックスを REMOVE する場合、両方は 元の リストに対して評価されるので、[2] と [3] を一緒に削除すると、期待どおり 3 番目と 4 番目の要素を落とします。
DELETE — セットのメンバーを取り除く
PromoCodes は文字列セットなので、顧客が 1 つのコードを取り除くときは REMOVE ではなく DELETE を使います。REMOVE PromoCodes はセット全体を吹き飛ばします。DELETE は名指しされたメンバーを差し引きます。
DELETE PromoCodes :pulled
:pulled = セット {"SAVE10"} なら、そのメンバーだけが消えます。ここで 2 つのルールが効いてきます。セットは決して空にできないので、最後のメンバーを削除すると PromoCodes 属性がまるごと削除されます。そして値は属性に一致するセット型でなければなりません — 裸の文字列は型エラーです。
組み合わせる
「項目を追加し、プロモを適用し、カウントを増やす」更新は、3 つの句にまたがる 1 回の呼び出しです。
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem),
OrderTotal = OrderTotal + :price
ADD ItemCount :one
DELETE PromoCodes :expiredCode
OrderTotal = OrderTotal + :price に注目してください — SET の中の算術は既存の値に対して働きます。これは ADD と同じくらいアトミックで競合安全です。DynamoDB は現在の値に対してサーバー側で OrderTotal + :price を評価するので、同時呼び出しはあなたのコードを往復するのではなく項目上で直列化されます。
避けるべき落とし穴
- 先に読み取ったカウンターを
SETする。ADDを使ってください — read-modify-write は並行実行下で更新を失います。これは最もよくあるカート/在庫のバグです。 - 存在しないリストへの
list_append。 対象をif_not_existsで包まないと、最初の書き込みが失敗します。 REMOVEとDELETEの混同。REMOVEは属性を落とします。DELETEはセットからメンバーを差し引きます。これらを取り違えると、意図した以上に削除します。UpdateItemがアップサートであることを忘れる。 キーが存在しなければ、項目を作成します。「更新だけ」を意図するならConditionExpression(attribute_exists(CartPK))を使ってください。
これらの式が実行される対象のキーをモデル化するにはシングルテーブル設計を、カートをどう読み戻すか決めるには query vs scan を参照してください。
これらのいずれも式ビルダーで構築してコピーし、DynoTable を試して自分自身のテーブルに対して実行し、項目がライブで変わるのを見てください。