今年(2025 年)7 月 5 日開催の JAWS ミート 2025 の LT で、表題のとおり Aurora DSQL の OCC(楽観的同時実行制御)を「利用」した簡単なゲームを実施しました。
この記事では、そのゲームの際に取得したログから、Aurora DSQL の OCC(楽観的同時実行制御)の動作を追って考察してみます。
Aurora DSQL とは?
AWS が 2024 年 12 月の re:Invent 2024 で発表した、SQL が使えるサーバレス大規模分散データベースです。
使える SQL 構文に制約はありますが、PostgreSQL とはワイヤープロトコル互換となっており、psql
コマンドや PostgreSQL 向けの各種管理アプリケーション、接続用ライブラリを使って接続可能です。
分散処理によるスケーラビリティや地理冗長 Active-Active クラスタによる耐障害性の高さをウリにしたデータベースですが、先日(2025 年 5 月下旬)一般提供が開始(GA) されました。
なお、プレビューから GA に至るまでにコードを Kotlin から Rust に書き換えたことが、Amazon.com CTO である Werner Vogels 博士 のブログ「All Things Distributed」の記事で語られています。
OCC(楽観的同時実行制御)とは?
Aurora DSQL では書き込み処理のスループット向上のために OCC(楽観的同時実行制御) を採用しています。
ブレビューが始まった頃に書いた記事で説明しているので詳細は省略しますが、一言でいうと ロックを使わない同時実行制御方式 です。
一般的な RDBMS ではロックを使う PCC(悲観的同時実行制御)を採用しているので、その動作の違いがアプリケーション設計に大きな影響を与えます。
JAWS ミート当日の LT では
こちらの資料を使って説明しながらゲーム 「最後にコミットした人が勝ち!」 を実施しました。
(ゲーム自体は Amazon Q CLI に作ってもらいました)
※14 〜 16 ページにあるリンク(ゲームの URL)の先はすでに閉鎖しています。
出題
ゲームの前に、
を出題した上で、以下のルールでゲームを開始しました。
ゲーム開始!
- 最初に名前を登録
- ゲームが始まったら制限時間内に攻撃ボタンを押す
- 押すと DSQL 上のテーブル行を
UPDATE
→ 1 秒待つ→COMMIT
- 同時に複数の人が攻撃した場合、
COMMIT
が成功した人が勝ち-
勝つと
UPDATE
→COMMIT
の待ち時間が 1 秒増える(最大 5 秒まで) - 負けると 1 秒にリセット
-
勝つと
- 押すと DSQL 上のテーブル行を
- 攻撃ボタンは時間内に何度押しても OK
-
制限時間内で一番最後に
COMMIT
した人が優勝!- ボタンを押した後の
COMMIT
が制限時間外なら攻撃失敗
- ボタンを押した後の
当日の様子は、こちらの参加記録ブログ記事後半部分に掲載しました。
ゲーム制作時点では東京+大阪+ソウルのマルチリージョンクラスタ構成が組めなかったので、東京リージョンのシングルリージョンクラスタ構成で Aurora DSQL を用意しました。
答え合わせ
ゲームの後、参加者に聞いてみたのですが、意外と理解されておらず「A」と思っていた方が多かったようです。
ゲーム後に残ったログから考察
というわけで、ここからがこの記事の本題です(前置きが長かった)。
当日のゲームは 1 分間で実施しましたが、察しの良い方ならお分かりのとおり、このゲームは
- 終了手前を狙ってコミットが完了するように攻撃する
- それ以前の時間帯に一切攻撃しない
- うっかり攻撃成功して「待ち時間」を増やさない
のがポイントです。
ただ、普通に考えると「終了の 1 秒ちょっと前」に攻撃したくなるのですが、実は 「さらにその 1 秒前」を狙って攻撃するのがポイントです。
実際のログは
ゲームは 2025/07/05 13:44:32.472 に開始され、2025/07/05 13:45:32.472 に終了しました。
この 1 分間に 28 人の参加者がいて、560 件以上のログが記録されていました。
ただ、実際の勝敗に直接影響したのは残り数秒間だけでしたので、その部分だけ 30 行分抜き出して見やすいように加工しました。
※コミット時刻は実際の時刻ではなく単純に攻撃開始時刻に待ち時間を加えたものになっています。
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
1 2025/07/05 13:45:27.831 成功 Xさん 1 2025/07/05 13:45:28.831
2 2025/07/05 13:45:27.859 失敗 Yさん 1 2025/07/05 13:45:28.859
3 2025/07/05 13:45:28.022 失敗 1 2025/07/05 13:45:29.022
4 2025/07/05 13:45:28.127 失敗 1 2025/07/05 13:45:29.127
5 2025/07/05 13:45:28.651 失敗 1 2025/07/05 13:45:29.651
6 2025/07/05 13:45:28.802 失敗 1 2025/07/05 13:45:29.802
7 2025/07/05 13:45:29.282 失敗 Xさん 2 2025/07/05 13:45:31.282
8 2025/07/05 13:45:29.319 成功 Yさん 1 2025/07/05 13:45:30.319
9 2025/07/05 13:45:29.337 失敗 1 2025/07/05 13:45:30.337
10 2025/07/05 13:45:29.383 失敗 1 2025/07/05 13:45:30.383
11 2025/07/05 13:45:29.416 失敗 1 2025/07/05 13:45:30.416
12 2025/07/05 13:45:29.485 失敗 1 2025/07/05 13:45:30.485
13 2025/07/05 13:45:29.514 失敗 1 2025/07/05 13:45:30.514
14 2025/07/05 13:45:29.985 失敗 1 2025/07/05 13:45:30.985
15 2025/07/05 13:45:30.519 失敗 Yさん 2 2025/07/05 13:45:32.519
16 2025/07/05 13:45:30.530 成功 Zさん 1 2025/07/05 13:45:31.530
17 2025/07/05 13:45:30.601 失敗 1 2025/07/05 13:45:31.601
18 2025/07/05 13:45:30.682 失敗 1 2025/07/05 13:45:31.682
19 2025/07/05 13:45:30.735 失敗 1 2025/07/05 13:45:31.735
20 2025/07/05 13:45:30.958 失敗 1 2025/07/05 13:45:31.958
21 2025/07/05 13:45:31.120 失敗 1 2025/07/05 13:45:32.120
22 2025/07/05 13:45:31.218 失敗 1 2025/07/05 13:45:32.218
23 2025/07/05 13:45:31.274 失敗 1 2025/07/05 13:45:32.274
24 2025/07/05 13:45:31.380 失敗 1 2025/07/05 13:45:32.380
25 2025/07/05 13:45:31.641 失敗 1 2025/07/05 13:45:32.641
26 2025/07/05 13:45:31.722 失敗 1 2025/07/05 13:45:32.722
27 2025/07/05 13:45:31.740 失敗 1 2025/07/05 13:45:32.740
28 2025/07/05 13:45:31.806 失敗 Xさん 1 2025/07/05 13:45:32.806
29 2025/07/05 13:45:32.431 失敗 1 2025/07/05 13:45:33.431
30 2025/07/05 13:45:32.851 失敗 1 2025/07/05 13:45:33.851
参加者については、勝敗の流れに直接影響した 3 名のみ、以下のとおりに表示しました。
- X さん : 最終的な勝者の 2 人前に攻撃成功
- Y さん : 最終的な勝者の 1 人前に攻撃成功
- Z さん : 最終的な勝者
以降、順を追って見ていきます。
No.1 〜 6 : X さん攻撃成功とその影響
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
1 2025/07/05 13:45:27.831 成功 Xさん 1 2025/07/05 13:45:28.831
2 2025/07/05 13:45:27.859 失敗 Yさん 1 2025/07/05 13:45:28.859
3 2025/07/05 13:45:28.022 失敗 1 2025/07/05 13:45:29.022
4 2025/07/05 13:45:28.127 失敗 1 2025/07/05 13:45:29.127
5 2025/07/05 13:45:28.651 失敗 1 2025/07/05 13:45:29.651
6 2025/07/05 13:45:28.802 失敗 1 2025/07/05 13:45:29.802
No.1 のタイミングで X さんが攻撃成功した(X さんのトランザクションのCOMMIT
が他の並行トランザクションのCOMMIT
に先行した)ことにより、
- 2025/07/05 13:45:27.831 〜 2025/07/05 13:45:28.831 の間にトランザクションが走っていて
- トランザクション開始がこの時間の範囲より前、またはこの範囲内
- 2025/07/05 13:45:28.831 より後に
COMMIT
が実行された
No.2(Y さん)〜 6 が攻撃失敗しています。
そして X さんが攻撃成功したことで、X さんの(次回攻撃時の)待ち時間が 2 秒に増えました。
No.7 〜 14 : Y さん攻撃成功とその影響
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
5 2025/07/05 13:45:28.651 失敗 1 2025/07/05 13:45:29.651
6 2025/07/05 13:45:28.802 失敗 1 2025/07/05 13:45:29.802
7 2025/07/05 13:45:29.282 失敗 Xさん 2 2025/07/05 13:45:31.282
8 2025/07/05 13:45:29.319 成功 Yさん 1 2025/07/05 13:45:30.319
9 2025/07/05 13:45:29.337 失敗 1 2025/07/05 13:45:30.337
10 2025/07/05 13:45:29.383 失敗 1 2025/07/05 13:45:30.383
11 2025/07/05 13:45:29.416 失敗 1 2025/07/05 13:45:30.416
12 2025/07/05 13:45:29.485 失敗 1 2025/07/05 13:45:30.485
13 2025/07/05 13:45:29.514 失敗 1 2025/07/05 13:45:30.514
14 2025/07/05 13:45:29.985 失敗 1 2025/07/05 13:45:30.985
関係するトランザクションが No.5 〜 14 の範囲になるので No.5・6 も含めて示しています。
No.5・6 ですが、No.8 の Y さんの攻撃(トランザクション)より先行して始まりUPDATE
もCOMMIT
も先行していますが、前述のとおり No.1 の X さんのCOMMIT
に遅れたために失敗しています。
たとえ先行COMMIT
しても失敗していれば並行トランザクションのCOMMIT
の成否には影響しませんので、結果として No.8 の Y さんのCOMMIT
は成功します。
No.7 の X さんの攻撃(BEGIN
〜UPDATE
)は Y さんに先行していますが、待ち時間が増えた結果 Y さんよりも後にCOMMIT
しているので失敗しています。
そして No.9 〜 14 は先の例と同様 Y さんのCOMMIT
に遅れたので失敗しています。
加えて Y さんが攻撃成功したことで、Y さんの次回待ち時間が 2 秒に増え、X さんが失敗したことで次回待ち時間が 1 秒にリセットされました。
No.16 〜 24 : Z さん攻撃成功とその影響
こちらも関係するトランザクションが No.14 〜 24 の範囲になるので No.14 も含めて示しています。
また失敗理由が「時間超過」なので本来はここから外れますが、No.15 も含めて考察します。
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
14 2025/07/05 13:45:29.985 失敗 1 2025/07/05 13:45:30.985
15 2025/07/05 13:45:30.519 失敗 Yさん 2 2025/07/05 13:45:32.519
16 2025/07/05 13:45:30.530 成功 Zさん 1 2025/07/05 13:45:31.530
17 2025/07/05 13:45:30.601 失敗 1 2025/07/05 13:45:31.601
18 2025/07/05 13:45:30.682 失敗 1 2025/07/05 13:45:31.682
19 2025/07/05 13:45:30.735 失敗 1 2025/07/05 13:45:31.735
20 2025/07/05 13:45:30.958 失敗 1 2025/07/05 13:45:31.958
21 2025/07/05 13:45:31.120 失敗 1 2025/07/05 13:45:32.120
22 2025/07/05 13:45:31.218 失敗 1 2025/07/05 13:45:32.218
23 2025/07/05 13:45:31.274 失敗 1 2025/07/05 13:45:32.274
24 2025/07/05 13:45:31.380 失敗 1 2025/07/05 13:45:32.380
No.14 は先の No.8(Y さん)に遅れたことによりトランザクションが失敗しているので No.16 の Z さんのトランザクション成否には影響しません。
No.15(Y さん)は前回の攻撃成功により待ち時間が 2 秒になった結果、COMMIT
のタイミングがゲーム終了時刻を過ぎてしまったので失敗しています。仮に No.8 で成功していなければ、待ち時間が 1 秒で時間内にCOMMIT
が完了し、かつ Z さんに先行するのでタイミング的には優勝でした(残念!)。
No.17 〜 24 は先の例と同様 Z さんのCOMMIT
に遅れたので失敗しています。
No.25 〜 30 : 時間切れ
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
25 2025/07/05 13:45:31.641 失敗 1 2025/07/05 13:45:32.641
26 2025/07/05 13:45:31.722 失敗 1 2025/07/05 13:45:32.722
27 2025/07/05 13:45:31.740 失敗 1 2025/07/05 13:45:32.740
28 2025/07/05 13:45:31.806 失敗 Xさん 1 2025/07/05 13:45:32.806
29 2025/07/05 13:45:32.431 失敗 1 2025/07/05 13:45:33.431
30 2025/07/05 13:45:32.851 失敗 1 2025/07/05 13:45:33.851
これらは Z さんのトランザクションと並行していませんが、COMMIT
のタイミングがゲーム終了時刻を過ぎてしまったので失敗しています。
図示すると
こんな感じになるでしょうか。
PCC(悲観的同時実行制御)の場合はどうなる?
※考察上 SQL 文の実行オーバーヘッドは無視します。
たとえば Aurora DSQL ではなく PostgreSQL を使うケースではどうなるか?を考えてみます。
といっても、同じ流れで処理する場合、一度の攻撃で 1 秒以上のロックが掛かり累積していく関係上、DB へのアクセスのタイムアウトが頻発してゲームにならないので、
- ゲームが始まったら制限時間内に攻撃ボタンを押す
- 押すと DSQL 上のテーブル行を
UPDATE
→ 1 秒待つ→COMMIT
- 同時に複数の人が攻撃した場合、
COMMIT
が成功した人が勝ち- 勝つと
UPDATE
→COMMIT
の待ち時間が 1 秒増える(最大 5 秒まで) - 負けると 1 秒にリセット
- 勝つと
- 押すと DSQL 上のテーブル行を
の部分を、
- ゲームが始まったら制限時間内に攻撃ボタンを押す
- 押すと PostgreSQL 上のテーブル行を
SELECT ... FOR UPDATE NOWAIT
- エラーが出なければ(ロックが取れたら)勝ち
-
UPDATE
→ 1 秒待つ→COMMIT
-
COMMIT
/ROLLBACK
前の待ち時間が 1 秒増える(最大 5 秒まで)
-
- エラーが出たら(ロックが取れなければ)負け
- 待つ→
ROLLBACK
-
COMMIT
/ROLLBACK
前の待ち時間が 1 秒にリセット
- 待つ→
- エラーが出なければ(ロックが取れたら)勝ち
- 押すと PostgreSQL 上のテーブル行を
に置き換えます。
すると、
No. 攻撃開始時刻 攻撃成否 参加者 待ち時間(秒) コミット時刻
7 2025/07/05 13:45:29.282 失敗 Xさん 2 2025/07/05 13:45:31.282
が「失敗」から「成功」に変わります。待ち時間の間、他の並行トランザクション(No.2 〜 6)はロックが取れず、エラーになるからです。
そして、No.8 〜 23 は No.7(X さん)のロックに阻まれロックが取れずに失敗します。
OCC ではトランザクション時間が長いほど不利でしたが、PCC ではトランザクション時間が長いほうが有利になります。
しかし、その直後
24 2025/07/05 13:45:31.380 失敗 1 2025/07/05 13:45:32.380
が X さんのCOMMIT
直後・終了直前の「すき間」に滑り込んで成功するので、勝者は No.24 の攻撃を行った参加者になります。
図示すると
こちらはこんな感じでしょうか。
というわけで
当日は資料の最終ページ↓を出し忘れたのですが、
通常の RDBMS とは異なる「Aurora DSQL ならではの特性」を理解した上で使いましょう!