こんにちは、カスタマーサクセス部 CS2課の畑野です。
先日、大阪万博の来場予約をしようとしたところ、システム上で順番待ちとなりました。 ようやく自分の番が来て来場日時を指定し予約を進めると、「すでに予約枠が埋まっています」とのメッセージが返ってきました。 日時選択時点では枠が空いていたように見えたのに、予約できなかったのは“早い者勝ち競争”に敗れてしまったからだと推測しています。
今回はこの体験をきっかけに、楽観的同時実行制御(以下、OCC)を採用している Aurora DSQL の特性を活かして、 万博予約のような“予約合戦”を再現できるかを検証してみました。
検証内容
宿泊予約サイトを作成し、OCCを意図的に体験できるシナリオを組みました。
一般的なRDBMSでは悲観的同時実行制御(PCC)を採用しており、トランザクション中にロックがかかります。 このため、先に処理を開始したユーザーの処理が完了するまで、後から処理を開始したユーザーは待たされることになります。
今回はOCCを体感することが目的なので、以下のような状況を再現します。
- Aさんが先に予約処理を開始する
- 後からBさんが予約処理を行い確定する
- システム上、Aさんのトランザクションに意図的な遅延を差し込み、後からトランザクションを開始したBさんの処理が先に完了するようにします ※意図的な遅延を発生させるため名前の長さを取得し、1文字あたり1秒遅延処理させます
シナリオ
前提: 同じ日付には1人しか予約できない仕様です。 名前はAさんの方を長くし、Aさんの予約処理が長くなるようにします。
- AさんとBさんが同じ日付で予約処理を開始
- Aさんが先に予約処理を開始
- Bさんが後から処理を開始
- Aさんが先に確定操作を行う
- Bさんも確定操作を行う
- AさんとBさんどちらの予約が成立するかを確認する
処理の流れ
- 予約開始
- 宿泊日の指定
- 到着時間の指定
- 名前の入力
- 予約確定(既に予約が存在する場合はエラーを返す)
Aさん 予約開始
予約を開始します。
日付を選択します。
到着時間を入力します。
名前を入力します。
予約を確定します。
Bさん 遅れて予約開始
予約を開始します。
日付を選択します。
到着時間を入力します。
名前を入力します。
予約を確定します。
処理を待ちます。
後から予約を開始したBさんの予約が確定しました。
一方、Aさんは
Bさんがすでに同じ日付で予約を確定していたため予約に失敗しました。
エラーの原因は、Bさんのトランザクションが先に成立した結果、OCC例外が発生し、失敗しました。
結果、Aさんが先にトランザクションを開始したにも関わらず、Bさんのトランザクションが先に完了したため、 Bさんの予約が確定することを確認できました。
構成

- CloudFrontのオリジンにS3バケットを指定し、静的コンテンツを配置(OAC経由)
- 予約ページにアクセスすると予約APIを通じて処理が行われる
- 予約成功後は予約一覧APIを通じて誰がいつ予約したかを表示
- データ保存先は Aurora DSQL を利用
通信の流れを簡単にするため認証処理は省略しており、OCCの体験にフォーカスしたシンプルな構成になっています(最低限のCORS対応のみ実装)。
総括
「OCCは“先にコミットした方が勝ち”」 この特徴を実際に体験できたのではないでしょうか。
世の中は“早い者勝ち”。 トラヴィス・スコットのスニーカー争奪戦に毎回敗れている私としては、なんとも身につまされる話です。
OCCの前提には「競合は稀である」という思想があります。 これはシンプルな実装につながる一方で、今回のように競合が発生した場合のエラーハンドリングが必須になると考えます。
操作後に失敗する可能性があるため、たとえば
- 「すでに予約されています」と事前に知らせる
- 「別日程に変更してみませんか?」とリトライを促す
といったユーザー体験を損なわない設計が重要になりそうです。 つまり、OCCの「楽観的」な思想を、ユーザーには“悲観的”に見せないUI/UXの設計が鍵です。
おまけ
Amazon Aurora DSQL の「楽観的同時実行制御(OCC)」を体験するための Web宿泊予約システム の構築テンプレートです。 静的な予約ページから API 経由で予約・一覧取得を行い、同一日付での重複予約防止などの挙動を検証できます。 src/db.pyがDSQLへの接続モジュールです。Lambdaで呼び出す際は適切なIAMロールを付与して下さい。 ※ブログ投稿時からコードが変更される可能性があります
参考資料
aws.amazon.com OCCの利点や競合時のリトライについて参照しました。