中級読了 2 分

DynamoDBのReturnValues:古いまたは新しいアイテムを取得する

デフォルトでは、DynamoDBの書き込みは成功以外に何も返しません。しかし、書き込みの 周辺のデータ — 変更前の値や、変更後の新しい値 — が必要になることはよくあります。 素朴な解決策は2回目のGetItemですが、これは余分な往復であり、競合でもあります: その間に別の誰かが書き込めるのです。DynamoDBはReturnValuesパラメータで両方を 回避します。これは書き込み自体の一部として、古いまたは新しいアイテムをアトミックに 返します。

DynamoDBのReturnValuesは何をするのか?

ReturnValuesは、DynamoDBの書き込みに対して同じ呼び出しの中でアイテムを返すよう指示します。これにより、2回目のGetItemとそれが生む競合状態を回避できます。PutItemDeleteItemNONEまたはALL_OLDのみを受け付け、UpdateItemは5つすべて(NONEALL_OLDUPDATED_OLDALL_NEWUPDATED_NEW)を受け付け、古いまたは新しい値をアトミックに返します。

  • ReturnValuesは書き込みの一部としてアイテムを返す — 2回目の読み取りも、競合もなし。
  • NONE(デフォルト) — 何も返さない。
  • ALL_OLD — 書き込み前の状態のアイテム全体。
  • UPDATED_OLD — 更新が変更した属性のみの、変更前の値。
  • ALL_NEW — 書き込み後のアイテム全体。
  • UPDATED_NEW — 変更された属性のみの、変更後の値。
  • PutItem/DeleteItemNONEまたはALL_OLDのみを受け付け、 UpdateItemは5つ すべてを受け付ける。

問題:今上書きした値が必要

サポートデスクを運用していて、担当者がチケットのステータスをopenからpendingに 変更するとします。監査ログには、変更前のステータスが何だったかを記録する必要が あります。ReturnValuesがなければ、こうするでしょう:

  1. 現在のステータスを読み取るGetItem
  2. 新しいものを設定するUpdateItem

ステップ1と2の間に別の担当者がステータスを変更するかもしれません — すると監査ログは 古い「変更前」の値を記録してしまいます。さらに悪いことに、1つの論理的な操作に対して 2回の呼び出しです。ReturnValuesはこれを、書き込み時点で実際にそうだった古いステータスを 返す、単一のアトミックなUpdateItemにまとめます。

5つの選択肢と、それぞれの使いどころ

UpdateItemはすべてをサポートします。選択は、アイテムのどの部分書き込みのどちら側が 必要かです:

ReturnValues返すもの使う場面
NONEなしアイテムを返す必要がない(デフォルト)
ALL_OLDアイテム全体、書き込み前監査 / 「今何を置き換えた?」
UPDATED_OLD変更した属性、書き込み前触れたフィールドだけ気にする場合
ALL_NEWアイテム全体、書き込み後呼び出し元に返す新しい完全なアイテムが必要な場合
UPDATED_NEW変更した属性、書き込み後今インクリメントしたカウンター/値を読み戻す場合

UPDATED_NEWは日常の主役です:更新式でカウンターを インクリメントし、同じ呼び出しで新しい合計を読み戻します。競合はありません。サポート チケットの監査では、ALL_OLD(ステータスフィールドだけをログに記録するなら UPDATED_OLD)が変更前の状態をアトミックに捉えます。

非対称性に注目してください:PutItemDeleteItemNONEALL_OLDのみをサポート します — deleteには返すべき「新しい」値がなく、putの新しい値はまさに送ったものだから です。その場で変更するUpdateItemだけが5つすべてを提供します。 AWSのドキュメント に正確な対応表があります。

DynoTableでの更新の書き込み

DynamoDB式ビルダーUpdateItemとその更新式を ビジュアルに組み立てましょう — SET/ADD句に加えて属性名と値のマップを出力します。 アプリ内では、ステージした書き込みがコミットされた後にDynoTableが結果のアイテムを表示 するので、新しい状態を直接確認できます。

DynoTable でアイテムのステージ済み変更を確認しているところ — 更新がコミットされる前の古い値と新しい値が表示されている。
DynoTable でアイテムのステージ済み変更を確認しているところ — 更新がコミットされる前の古い値と新しい値が表示されている。

落とし穴と次のステップ

  • 変更の周辺を読むためにGetItemしてから書き込んではいけない — 往復であり競合です。 ReturnValuesを使いましょう。
  • UPDATED_*は触れた属性のみを返す — アイテム全体が必要ならALL_*を使いましょう。
  • PutItem/DeleteItemは新しい値を返せないNONE/ALL_OLDのみです。
  • ReturnValuesは条件の代わりにはならない — 書き込みをガードするには 条件式を追加し、その効果を読み戻すには ReturnValuesを使いましょう。両者は組み合わせられます。
  • 関連: 更新式アトミックカウンター

2回の呼び出しをスクリプトで書かずに編集を行い、変更前後を確認したいですか? DynoTableをダウンロードして、アイテムを直接編集してください。

UPDATED_NEWによるアトミックカウンター

在庫システムは書き込みのたびにversionstockフィールドをインクリメントします。 そのパターンは、ADD stock :incReturnValues: UPDATED_NEWを指定した1回の UpdateItemです:

UpdateItem  PK=SKU#8842
  UpdateExpression: ADD stock :one
  ExpressionAttributeValues: {":one": {"N": "1"}}
  ReturnValues: UPDATED_NEW
Attributes.stock.N == "41"   (was 40)

返ってくるのは変更された属性のマップだけで、アイテム全体ではありません — アイテムが 大きく、呼び出し元は新しいカウンターだけを必要とする場合に最適です。変更前のすべての フィールドを記録しなければならない監査証跡には、ALL_OLDに切り替えましょう。

書き込みの課金は引き続きアイテムサイズに基づくUpdateItemとして行われます。 ReturnValuesが読み取り料金を別途追加することはありません — DynamoDBは更新を適用する ためにすでにアイテムを読み込んでいるからです。

キャパシティに関する注意

属性を返しても、書き込み自体のWCUコストが2倍になることはありません。AWSのルールに従い、 更新前後のアイテムサイズに基づいて書き込み分を支払います。レスポンスのペイロードに いくつの属性が現れるかとは無関係です。

古い値を記録するためにGetItemしてからUpdateItemしようとしていたなら、読み取り1回分と 書き込み1回分を支払っていたことになります。更新にReturnValues: ALL_OLDを付ければ、 読み取りは完全になくなります — 2 KBのアイテムを毎秒500回更新する場合、結果整合性の 読み取りでおよそ毎秒250 RCUの節約になります。

条件式と組み合わせる

ReturnValues条件式は、同じ呼び出しの中で 組み合わせられます。例:上限を下回っている間だけretryCountをインクリメントし、新しい カウントを返す:

ConditionExpression: retryCount < :max
UpdateExpression: ADD retryCount :one
ReturnValues: UPDATED_NEW

条件が失敗した場合、DynamoDBはConditionalCheckFailedExceptionを返し、属性のペイロードは 返しません — 何も変わらなかったためにUPDATED_NEWが空になる、成功した更新とは別物です。

式ビルダーを使えば、UpdateExpression、条件、 マーシャリングされた値のマップをまとめて生成できます。

選び方ガイド

必要なもの設定使えるオペレーション
何も返さないNONEPut, Update, Delete
上書き/削除の前のアイテム全体ALL_OLDPut, Update, Delete
変更したフィールドのみ、変更前UPDATED_OLDUpdate
パッチ適用後のアイテム全体ALL_NEWUpdate
変更したフィールドのみ、変更後UPDATED_NEWUpdate

削除とput

ReturnValues: ALL_OLDを付けたDeleteItemは、キューのアイテムに対して「取り出して返す」 セマンティクスを実装する方法です — 削除された行がAttributesに返ってきます。deleteに ALL_NEWがないのは、アイテムがもう存在しないからです。

ALL_OLDを付けたPutItemは、既存のキーを上書きしたときに以前のアイテムを返します — 入れ替えのワークフローに便利です。キーが存在しなかった場合、レスポンスにAttributesは 含まれません。

DynoTableで確認する

アイテムエディタで属性の変更をステージしてみましょう:レビューペインが、コミット前に 古い値と新しい値を並べて表示します — UPDATED_OLDUPDATED_NEWが返すのと同じ情報を、 スクリプトを書かずに得られます。コミット後は、グリッドのエクスポート操作から行をJSONと してコピーし、テストフィクスチャに使えます。

更新日