DynamoDB で在庫数を管理したい。
占有ロックを取らずに、同時更新が走った際にマイナスになる更新のみFailさせる方法を試してみる。
在庫管理で占有ロックを取らない実装
在庫数をDBで管理する際に、占有ロックを取らずに更新時にマイナスになる場合だけ Fail させる実装ができると、ロック時間を短縮でき高トラフィックへのスケーラビリティが出てくる。
在庫引き当て時に失敗していい(先勝ち)要件であれば、この実装が望ましい。
※在庫数確認した時点で在庫があれば、絶対に在庫引き当てができなければいけない、というような要件だと在庫確認時点から占有ロックを取る必要がある。
DynamoDB でやる場合、update-item による更新時の条件を Condition Expression で指定できるため、在庫数がマイナスの際には更新を失敗させる実装が可能。
実装
サンプルのテーブル構造
Store (PK) | Item (SK) | Count |
---|---|---|
Calcutta | Echo (2nd Gen) | 10 |
hoge | hoge | 10 |
Itemごとに在庫を持つシンプルな構成。
※実際に高トラフィックを想定すれば、このままだとホットパーティションになってしまうので、PK を複数に分割する等の検討は必要。
更新時の update-item と Condition Expression
在庫数(Count
)をデクリメントする update-item
更新時に在庫数を確認し、0より大きければ更新をする Condition Expression を付与している。
$ aws dynamodb update-item --table-name Inventory \ --key '{"Store": {"S": "Calcutta"}, "Item": {"S": "Echo (2nd Gen)"}}' \ --expression-attribute-names '{"#Count":"Count"}' \ --expression-attribute-values '{":num": {"N":"-1"}, ":limit":{"N":"0"}}' \ --update-expression "set #Count=#Count+:num" \ --condition-expression "#Count > :limit"
在庫数が0の場合以下のようなエラーが返ってくる。
An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request **failed**
※今回 Update Expression には SET を利用しているが、デクリメントしたいだけなので ADD のほうがいい
通常は、SET ではなく ADD を使用することをお勧めします。
※以下にあるようにNW障害で update-item オペレーションの結果が返ってこない場合等において、実際に DynamoDB 側で更新がされたかに関わらず、アプリケーションが再試行をする可能性がある。
その場合、2度更新がされてしまい、実体よりも在庫が少なくなってしまう可能性がある。
UpdateItem オペレーションが失敗した場合、アプリケーションはオペレーションを再試行します。これには、カウンターを 2 度更新する恐れがあります
同時更新で試してみた
tmux の同時実行を使って、複数セッションで同時更新をかけてみて、ちゃんとマイナスにならずに更新される or Fail されるかを確認してみた。
在庫10に対して、14セッションで更新をかけた。
DynamoDB の Condition Expression を使ってマイナス時Failさせる
— kashinoki38 (@ka_shino_ki) 2022年3月9日
のを複数セッションでやってみた pic.twitter.com/ngfMlPSrFV