DynamoDBのReturnValues:古いまたは新しいアイテムを取得する
デフォルトでは、DynamoDBの書き込みは成功以外に何も返しません。しかし、書き込みの
周辺のデータ — 変更前の値や、変更後の新しい値 — が必要になることはよくあります。
素朴な解決策は2回目のGetItemですが、これは余分な往復であり、競合でもあります:
その間に別の誰かが書き込めるのです。DynamoDBはReturnValuesパラメータで両方を
回避します。これは書き込み自体の一部として、古いまたは新しいアイテムをアトミックに
返します。
DynamoDBのReturnValuesは何をするのか?
ReturnValuesは、DynamoDBの書き込みに対して同じ呼び出しの中でアイテムを返すよう指示します。これにより、2回目のGetItemとそれが生む競合状態を回避できます。PutItemとDeleteItemはNONEまたはALL_OLDのみを受け付け、UpdateItemは5つすべて(NONE、ALL_OLD、UPDATED_OLD、ALL_NEW、UPDATED_NEW)を受け付け、古いまたは新しい値をアトミックに返します。
ReturnValuesは書き込みの一部としてアイテムを返す — 2回目の読み取りも、競合もなし。NONE(デフォルト) — 何も返さない。ALL_OLD— 書き込み前の状態のアイテム全体。UPDATED_OLD— 更新が変更した属性のみの、変更前の値。ALL_NEW— 書き込み後のアイテム全体。UPDATED_NEW— 変更された属性のみの、変更後の値。PutItem/DeleteItemはNONEまたはALL_OLDのみを受け付け、UpdateItemは5つ すべてを受け付ける。
問題:今上書きした値が必要
サポートデスクを運用していて、担当者がチケットのステータスをopenからpendingに
変更するとします。監査ログには、変更前のステータスが何だったかを記録する必要が
あります。ReturnValuesがなければ、こうするでしょう:
- 現在のステータスを読み取る
GetItem、 - 新しいものを設定する
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)が変更前の状態をアトミックに捉えます。
非対称性に注目してください:PutItemとDeleteItemはNONEとALL_OLDのみをサポート
します — deleteには返すべき「新しい」値がなく、putの新しい値はまさに送ったものだから
です。その場で変更するUpdateItemだけが5つすべてを提供します。
AWSのドキュメント
に正確な対応表があります。
DynoTableでの更新の書き込み
DynamoDB式ビルダーでUpdateItemとその更新式を
ビジュアルに組み立てましょう — SET/ADD句に加えて属性名と値のマップを出力します。
アプリ内では、ステージした書き込みがコミットされた後にDynoTableが結果のアイテムを表示
するので、新しい状態を直接確認できます。

落とし穴と次のステップ
- 変更の周辺を読むために
GetItemしてから書き込んではいけない — 往復であり競合です。ReturnValuesを使いましょう。 UPDATED_*は触れた属性のみを返す — アイテム全体が必要ならALL_*を使いましょう。PutItem/DeleteItemは新しい値を返せない —NONE/ALL_OLDのみです。ReturnValuesは条件の代わりにはならない — 書き込みをガードするには 条件式を追加し、その効果を読み戻すにはReturnValuesを使いましょう。両者は組み合わせられます。- 関連: 更新式、 アトミックカウンター。
2回の呼び出しをスクリプトで書かずに編集を行い、変更前後を確認したいですか? DynoTableをダウンロードして、アイテムを直接編集してください。
UPDATED_NEWによるアトミックカウンター
在庫システムは書き込みのたびにversionやstockフィールドをインクリメントします。
そのパターンは、ADD stock :incとReturnValues: 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、条件、
マーシャリングされた値のマップをまとめて生成できます。
選び方ガイド
| 必要なもの | 設定 | 使えるオペレーション |
|---|---|---|
| 何も返さない | NONE | Put, Update, Delete |
| 上書き/削除の前のアイテム全体 | ALL_OLD | Put, Update, Delete |
| 変更したフィールドのみ、変更前 | UPDATED_OLD | Update |
| パッチ適用後のアイテム全体 | ALL_NEW | Update |
| 変更したフィールドのみ、変更後 | UPDATED_NEW | Update |
削除とput
ReturnValues: ALL_OLDを付けたDeleteItemは、キューのアイテムに対して「取り出して返す」
セマンティクスを実装する方法です — 削除された行がAttributesに返ってきます。deleteに
ALL_NEWがないのは、アイテムがもう存在しないからです。
ALL_OLDを付けたPutItemは、既存のキーを上書きしたときに以前のアイテムを返します —
入れ替えのワークフローに便利です。キーが存在しなかった場合、レスポンスにAttributesは
含まれません。
DynoTableで確認する
アイテムエディタで属性の変更をステージしてみましょう:レビューペインが、コミット前に
古い値と新しい値を並べて表示します — UPDATED_OLDとUPDATED_NEWが返すのと同じ情報を、
スクリプトを書かずに得られます。コミット後は、グリッドのエクスポート操作から行をJSONと
してコピーし、テストフィクスチャに使えます。


