Dev Study
AWSサービスの内部原理 コース

5. 結果整合性とアトミックカウンター — 分散の一貫性と競合状態

「複製の伝播遅延」と「並行更新の排他」——すでに学んだ2つの基礎で DynamoDB の挙動を図で追っていきましょう。

①書き込みは複数AZに複製される

結果整合性の正体
書き込み成功HTTP 200 = 確実に保存済み
コピーへ伝播(わずかなラグ)
AZ-A反映済み
AZ-B反映済み
AZ-Cまだ古い
  • ・コピー間の反映ラグ=結果整合性(eventually consistent)
  • ・複数のコピーを持つ仕組みなら必ず生じる普遍的な性質
  • ・分散システムで学んだ「複製の伝播遅延」そのもの

書き込みは複数のアベイラビリティーゾーンに複製される。コピー間の反映ラグこそが結果整合性の正体。

②読み取りは2モード

どのコピーの値が返るか
get-item(読み取り)
モードを選ぶ
結果整合性(既定)一瞬古い値が返りうる
待てば追いつく
強い整合性ConsistentRead=true
最新の値を返す
  • ・公式の保証は「返る値が最新」という結果のみ(内部手順ではない)
  • ・強い整合性はテーブルとLSIのみ。GSIとストリームでは使えない

既定は結果整合性で、伝播途中のコピーを読むと一瞬古い値が返る。強い整合性は成功した全書き込みを反映した最新値を返す。

③料金の事実は一点、そこから使い分け

結果整合性は強い整合性の半額
多少古くてもOK例: いいね数の表示
書いた値を確実に読み返す例: 直後の読み直し
選ぶ
結果整合性半額(1x)
強い整合性2倍(2x)

公式が述べる事実は「結果整合性は半額」の一点。最新でなくてよい読み取りは安い方を選ぶ、が導ける設計判断。

④同時に書くと壊れる(競合状態)

読む→足す→書き戻す を各自でやると
A: 読む → 100
B: 読む → 100
各自 +1 して書き戻す
A: 101 を書く
B: 101 を書く
後の書き込みが上書き
結果: 101片方の +1 が消えた
  • ・両者が同じ古い値を読むのが原因(Race Condition)
  • ・OSで学んだ「同じ資源を複数が読み書きすると壊れる」と同じ構図

2つのクライアントが同じ古い値を読んでから書き戻すと、片方の増加がもう片方に上書きされて消える。

⑤アトミックカウンター:増分だけ送る

UpdateItem の更新式(ADD / SET Price = Price + :incr)
A: 「+1」だけ送る
B: 「+1」だけ送る
読み取り・加算・書き込みをサーバーに任せる
DynamoDB サーバー不可分な1操作として受信順に適用
結果: 102両方の増加が反映
  • ・「すべての書き込みは受信した順に適用される」(公式)
  • ・クリティカルセクションの排他をマネージドサービスが肩代わり
  • ・ただし冪等ではない: 同じリクエストを2回送れば2回増える

「いくつ増やすか」だけをサーバーに伝え、サーバー側が不可分な1操作として処理する。再送で二重加算が起こりうるため、多少の誤差を許せる用途向き。

⑥厳密さが要るなら条件付き書き込み

条件はサーバー側で最新値に対して評価される
アリス: 更新成功条件 Price=10 → 成立
Price はもう 10 ではない
ボブ: 更新は失敗条件 Price=10 → 不成立
  • ・条件なしだと、後から書いたボブがアリスの更新を無自覚に上書き
  • ・条件付きなら更新の取りこぼし(lost update)を防げる
  • ・銀行残高のように絶対にずれてはいけない値はこちら

「現在値が10のときだけ更新」と条件を付ければ、間に他人の更新が入っていれば失敗する。結果整合性も排他も、「複製の伝播遅延」と「並行更新の排他」の2つの基礎から再導出できる。

サンプルコード(フレームワーク環境が必要なため表示のみ)

// 結果整合性読み取り(既定): 直前の書き込みが未反映のことがある。安く、少し待てば追いつく
// 強い整合性読み取り: 成功した全書き込みを反映した最新値を返す(ConsistentRead=true / 結果整合性の2倍のコスト)
aws dynamodb get-item --table-name ProductCatalog --key '{"Id":{"N":"1"}}' --consistent-read

// アトミックカウンター: 読み取り→加算→書き戻しをサーバー側の不可分な1操作にまとめる
//   受信順に適用される / 冪等ではない(再送すると二重加算)ため、多少の誤差を許せる用途向け
aws dynamodb update-item --table-name ProductCatalog --key '{"Id":{"N":"601"}}' \
  --update-expression "SET Price = Price + :incr" \
  --expression-attribute-values '{":incr":{"N":"5"}}'

// 条件付き書き込み: 「現在値が10のときだけ更新」。間に他人の更新が入っていれば失敗し、取りこぼしを防ぐ
aws dynamodb update-item --table-name ProductCatalog --key '{"Id":{"N":"1"}}' \
  --update-expression "SET Price = :newval" \
  --condition-expression "Price = :currval" \
  --expression-attribute-values '{":newval":{"N":"8"},":currval":{"N":"10"}}'
公式ドキュメントで詳しく ↗