トランザクションの競合とデータ整合性
DSQLの競合・再試行は整合性・レイテンシ・実装量にどのような影響を与えるか?
結果の要約
この実験では、同じ行を複数のリクエストが同時に更新するときにDSQLと対照群が整合性を守るか、また競合と再試行がレイテンシと失敗率にどのような影響を与えるかを確認しました。注文作成と口座振替の業務を、同時実行数16/64/256、均等アクセスと人気キーへの集中アクセスに分けて実行し、負荷セルごとに在庫・残高・重複処理の不変条件を検査しました。66個の負荷セルすべてで不変条件の違反は0件でした。4サービスとも、条件付き更新、業務IDのレシート、再試行、コミット有無の確認を実装すれば、二重処理やコミットの消失なしに動作しました。
競合の扱い方は大きく異なりました。DSQLはロック待機なしに、コミット時点で競合(40001)を通知します。そのため、上位1%のキーにリクエストの80%が集中する集中競合では、コミットに成功したリクエストのp99は110 ms以下と短かったものの、同時実行数256の競合率は67–71%で、最大3回の再試行後も39%が最終的に失敗しました。対照群は行ロックでリクエストを順番待ちさせ、同時実行数64と256の集中競合では待機が2秒の期限を超えて、成功スループットが24–512 TPSに落ち、最終失敗率が18–63%になりました。DSQLはREAD COMMITTEDをサポートしないため再試行の実装は必須であり、人気商品のように競合が集中する業務では、再試行の予算と競合を分散するスキーマ設計を併せて検討する必要があります。
費用上限(当時USD 5、後に50へ引き上げ)のため対照群をバースト可能な小型インスタンスに縮小し、セルは25秒ずつ1回だけ実行しました。したがってスループットの差はサービス比較の根拠には使わず、固定同時実行数の方式であるため、リクエストが滞留している間のレイテンシが漏れる可能性があります。計画規模での容量とレイテンシの比較は、E002でリクエスト率を固定する方式で再測定します。使用量から計算した推定費用は約USD 3.4で、Cost Explorerで確認した実際の請求額は約USD 2.07です(主な差はDSQLの月間無料使用量の差し引き)。
本番利用の観点
再試行と業務IDのレシート(冪等処理)を実装すれば、DSQLも競合状況で二重処理やコミットの消失なしに整合性を守ったため、整合性自体は本番導入の障害ではありませんでした。競合が分散した業務では競合率は既存サービスと同程度で、再試行でほとんど解消されました。しかし人気商品の在庫のように少数の行に書き込みが集中すると、再試行後も失敗が大きく残るため(同時実行数256で39%)、こうした業務は在庫行の分割のようなスキーマの再設計なしにはそのまま移すのが困難です。トランザクションあたり3,000行の制限のため大量削除・整理作業を分割する必要があり、サーバーエラーも1回観測されたため、再試行は必須です。小型の対照群・反復1回の結果であるため、容量の判断はE002で行います。
確認する問い
DSQLの競合・再試行は整合性・レイテンシ・実装量にどのような影響を与えるか?同じ注文・振替業務をD1(Aurora DSQL)と対照群3種(R1 RDS PostgreSQL Multi-AZ、A1 Aurora Provisioned、A2 Aurora Serverless v2)で実行しました。次の2点を確認しました。
- 分離レベルのシナリオ: 2つの接続の実行順序をbarrierで固定して5種類の異常現象を再現し、各サービスがそれを防ぐかを確認しました。
- 競合負荷: 同時実行数とアクセス分布を変えながら負荷をかけ、セルごとに業務不変条件を検査しました。
実行条件
- 測定日時: 2026-09-25 22:50 UTC – 2026-09-26 01:57 UTC、リージョン
ap-northeast-2、実行prefixe004-20260925t221414z-e5fd。 - コードコミット: 負荷セルは
9474ed6で実行しました。先にe3b71f9で実行したD1のセル5個も結果に含めています。2つのコミットの間でランナーのコードは変わっておらず、運用ツールの記録方式だけが変わりました。再現手順は実験READMEにあります。 - 比較構成: 費用上限のため計画より縮小しました。
| ID | 実際の構成 | 計画構成との違い |
|---|---|---|
| D1 | DSQLシングルリージョンクラスター、IAMトークン、verify-full TLS、パブリックエンドポイント |
なし |
| R1 | RDS PostgreSQL 16.15、db.t4g.medium Multi-AZ、gp3 20 GiB、プライベートエンドポイント |
クラス(バースト可能 2 vCPU)・ストレージを縮小 |
| A1 | Aurora PostgreSQL 16.15、db.t4g.medium writer 1台、I/O-Optimized |
readerなし、小型クラス、Standardの代わりにI/O-Optimized |
| A2 | Aurora Serverless v2 16.15、writer 1台、0.5–4 ACU、I/O-Optimized | readerなし、ACU範囲を縮小 |
- 負荷発生器: 同じVPC内のEC2
c7g.4xlargeSpotインスタンス(16 vCPU)で、アクセスはSSMのみで行いました。ツールのバージョンはPython 3.11.16、psycopg 3.3.6(libpq 18)、boto3 1.43です。測定区間の発生器のCPU使用率は最大75%で、飽和により無効としたセルはありません。 - RTTの中央値: D1 2.6 ms、R1 0.7 ms、A1 0.2 ms、A2 0.16 ms。D1だけがパブリックエンドポイントを経由するため、RTTが最も長くなります。
- 業務: 注文作成70%(条件付きの在庫減算、注文・明細・レシートの保存)と口座振替30%(残高確認後に2口座を更新)です。SQLは4サービスで同一です。業務IDのレシートで重複処理を防ぎ、コミットされたかが不確かな場合はこのレシートを照会して判定します。
- 条件マトリクス:
- REPEATABLE READ(4サービス共通):均等・集中分布 × 同時実行数16/64/256 × 再試行なし/最大3回。
- READ COMMITTED:対照群のみ実行しました(対照群の既定の分離レベル、再試行最大3回)。D1は既定の分離レベルがREPEATABLE READであるため、共通マトリクスの結果をそのまま使います。
- 集中分布は、上位1%のキーにリクエストの80%を送ります。
- 再試行は
40001/40P01にのみ適用し、合計2秒の期限、指数バックオフ(基準10 ms)とfull jitterを使います。
- セルと反復: セルはウォームアップ5秒 + 測定20秒で、反復は1回です。同時実行数を固定し、応答が来たらすぐ次のリクエストを送る方式(closed-loop)です。
- 計画から変わった点:
- 計画はウォームアップ10分 + 収集20分を3回繰り返すものでした。
- D1のパイロットで、DSQLは試行1回あたり約0.095 DPUを消費しました。計画条件で実行するとD1のDPU費用だけで約USD 16.5と推定されたため、ユーザーの決定で反復回数とセル時間を減らしました。
- パイロットで
c7g.2xlargeが同時実行数256で飽和したため、ランナーをc7g.4xlargeに変更しました。 - 実行途中でSpotインスタンスが回収されたため(23:19 UTC、容量不足)、ランナーを交換し、D1の残りのセルから再開しました。
- D1のセル1個(
D1-RR-hot-c64-retry3)はDSQLのサーバーエラー(InternalError_: server unavailable、22:57 UTC)で失敗したため、もう1回実行しました。最初の試行の記録も保管しています。
性能結果
分離レベルのシナリオ
2つのトランザクションの実行順序を固定し、結果を記録しました。結果は「現象発生(anomaly)」「エラーで阻止(SQLSTATE)」「待機で阻止」のいずれかです。括弧内はHermitage式の表記です。D1はBEGIN ISOLATION LEVEL READ COMMITTEDとSERIALIZABLEを拒否したため(0A000)、該当する行は適用不可です。
| シナリオ | 分離レベル | D1 | R1 · A1 · A2(3構成とも同じ) |
|---|---|---|---|
| lost update (P4) | READ COMMITTED | 適用不可 | 現象発生 |
| lost update (P4) | REPEATABLE READ | エラーで阻止 (40001) | エラーで阻止 (40001) |
| lost update (P4) | SERIALIZABLE | 適用不可 | エラーで阻止 (40001) |
| write skew (G2-item) | READ COMMITTED | 適用不可 | 現象発生 |
| write skew (G2-item) | REPEATABLE READ | エラーで阻止 (40001) | 現象発生 |
| write skew (G2-item) | SERIALIZABLE | 適用不可 | エラーで阻止 (40001) |
| 同じ業務IDの二重注文 | READ COMMITTED | 適用不可 | 待機後に阻止 (23505) |
| 同じ業務IDの二重注文 | REPEATABLE READ | エラーで阻止 (40001) | 待機後に阻止 (23505) |
| 同じ業務IDの二重注文 | SERIALIZABLE | 適用不可 | 待機後に阻止 (40001) |
| 交差更新(デッドロック) | 3レベルとも | コミット時に阻止 (40001, RR) | デッドロック検出 (40P01) |
SELECT FOR UPDATE 条件付き減算 |
READ COMMITTED | 適用不可 | 待機後に順次処理 |
SELECT FOR UPDATE 条件付き減算 |
REPEATABLE READ / SERIALIZABLE | コミット時に阻止 (40001, RR) | 待機後に阻止 (40001) |
- 対照群の3構成: PostgreSQLのドキュメントに記載された分離レベルごとの動作とすべて一致しました。たとえばREPEATABLE READではwrite skewを許容し、デッドロックは
40P01で終了しました。 - D1の2つの違い:
- ロック待機がありません。 同じ行を2つのトランザクションが更新すると、先にコミットした方が成功し、後からコミットした方が
40001で失敗しました。FOR UPDATEをかけても、他のトランザクションは待機しませんでした。 - 今回のwrite skewシナリオをREPEATABLE READで
40001により阻止しました。 PostgreSQLのREPEATABLE READは同じシナリオを許容しました。
- ロック待機がありません。 同じ行を2つのトランザクションが更新すると、先にコミットした方が成功し、後からコミットした方が
- 解釈の限界: 2番は1回観測した結果です。これがDSQLの同時実行制御が常に保証する動作かどうかは、公式ドキュメントで確認していません。したがって「DSQLはwrite skewを防ぐ」とは一般化しません。
競合負荷
下の表で使う指標の定義は次のとおりです。
- 成功TPS: 測定20秒の間にコミットに成功した業務の数 ÷ 20。
- 生の競合率: 全試行のうち
40001/40P01で失敗した割合。 - 最終失敗率: 再試行と期限まで反映した後、最終的に失敗した業務の割合。品切れ・残高不足のような業務上の拒否は0件でした。
- p99 全体: 再試行と待機を含め、業務1件が終わるまでにかかった時間です。失敗した業務も含みます。2秒の期限に達した業務は、終わるまでに接続の入れ替えとレシート照会の時間が加わるため、2秒を超える値が出ることがあります。
- p99 コミット成功: コミットに成功した業務だけを集めたp99です。
すべてのセルが反復1回であるため、ばらつきは分かりません。各値は1回測定した結果です。
REPEATABLE READ · 再試行なし
| 分布 | 同時実行数 | 構成 | 成功TPS | 生の競合率 | 最終失敗率 | p50 (ms) | p99 全体 (ms) | p99 コミット成功 (ms) |
|---|---|---|---|---|---|---|---|---|
| 均等 | 16 | D1 | 841 | 0.6% | 0.6% | 19 | 28 | 28 |
| 均等 | 16 | R1 | 1,500 | 0.5% | 0.5% | 10 | 23 | 23 |
| 均等 | 16 | A1 | 855 | 0.6% | 0.6% | 18 | 44 | 44 |
| 均等 | 16 | A2 | 990 | 0.5% | 0.5% | 11 | 59 | 59 |
| 均等 | 64 | D1 | 2,363 | 2.5% | 2.5% | 26 | 39 | 39 |
| 均等 | 64 | R1 | 1,745 | 2.6% | 2.6% | 34 | 80 | 81 |
| 均等 | 64 | A1 | 1,050 | 2.1% | 2.1% | 56 | 152 | 152 |
| 均等 | 64 | A2 | 922 | 2.4% | 2.4% | 69 | 186 | 186 |
| 均等 | 256 | D1 | 8,494 | 8.6% | 8.6% | 27 | 41 | 41 |
| 均等 | 256 | R1 | 1,412 | 9.1% | 9.1% | 159 | 341 | 345 |
| 均等 | 256 | A1 | 1,165 | 8.4% | 8.4% | 192 | 400 | 400 |
| 均等 | 256 | A2 | 0 | 0.0% | 100.0% | 7,098 | 8,449 | - |
| 集中 | 16 | D1 | 711 | 20.8% | 20.8% | 18 | 25 | 25 |
| 集中 | 16 | R1 | 890 | 21.6% | 21.6% | 10 | 25 | 22 |
| 集中 | 16 | A1 | 518 | 19.8% | 19.8% | 15 | 81 | 44 |
| 集中 | 16 | A2 | 608 | 20.1% | 20.1% | 9 | 73 | 65 |
| 集中 | 64 | D1 | 1,402 | 43.7% | 43.7% | 25 | 36 | 35 |
| 集中 | 64 | R1 | 423 | 43.5% | 44.0% | 27 | 1,020 | 208 |
| 集中 | 64 | A1 | 274 | 39.6% | 41.0% | 43 | 4,536 | 335 |
| 集中 | 64 | A2 | 302 | 42.1% | 42.2% | 43 | 1,020 | 678 |
| 集中 | 256 | D1 | 4,479 | 66.5% | 66.5% | 19 | 31 | 28 |
| 集中 | 256 | R1 | 512 | 62.1% | 63.1% | 81 | 1,852 | 639 |
| 集中 | 256 | A1 | 56 | 17.3% | 42.5% | 109 | 16,211 | 373 |
| 集中 | 256 | A2 | 37 | 1.5% | 30.7% | 64 | 18,267 | 208 |
REPEATABLE READ · 再試行最大3回
| 分布 | 同時実行数 | 構成 | 成功TPS | 生の競合率 | 最終失敗率 | p50 (ms) | p99 全体 (ms) | p99 コミット成功 (ms) |
|---|---|---|---|---|---|---|---|---|
| 均等 | 16 | D1 | 841 | 0.6% | 0.0% | 19 | 39 | 39 |
| 均等 | 16 | R1 | 1,447 | 0.6% | 0.0% | 10 | 30 | 30 |
| 均等 | 16 | A1 | 869 | 0.5% | 0.0% | 16 | 54 | 54 |
| 均等 | 16 | A2 | 989 | 0.5% | 0.0% | 10 | 62 | 62 |
| 均等 | 64 | D1 | 2,268 | 2.5% | 0.0% | 28 | 65 | 65 |
| 均等 | 64 | R1 | 1,760 | 2.5% | 0.0% | 33 | 94 | 94 |
| 均等 | 64 | A1 | 1,082 | 2.1% | 0.0% | 54 | 162 | 162 |
| 均等 | 64 | A2 | 933 | 2.6% | 0.0% | 70 | 197 | 197 |
| 均等 | 256 | D1 | 8,230 | 9.8% | 0.4% | 28 | 96 | 92 |
| 均等 | 256 | R1 | 1,404 | 10.2% | 0.4% | 165 | 442 | 433 |
| 均等 | 256 | A1 | 700 | 7.6% | 2.3% | 173 | 12,641 | 545 |
| 均等 | 256 | A2 | 120 | 1.8% | 0.0% | 98 | 325 | 325 |
| 集中 | 16 | D1 | 421 | 27.2% | 4.9% | 27 | 109 | 104 |
| 集中 | 16 | R1 | 534 | 27.1% | 5.0% | 10 | 970 | 62 |
| 集中 | 16 | A1 | 343 | 25.9% | 5.2% | 15 | 990 | 120 |
| 集中 | 16 | A2 | 374 | 23.9% | 4.5% | 11 | 990 | 144 |
| 集中 | 64 | D1 | 1,102 | 50.4% | 18.0% | 29 | 110 | 105 |
| 集中 | 64 | R1 | 187 | 48.8% | 19.9% | 34 | 3,502 | 1,172 |
| 集中 | 64 | A1 | 190 | 47.0% | 18.0% | 51 | 4,581 | 1,104 |
| 集中 | 64 | A2 | 96 | 40.4% | 21.9% | 68 | 5,011 | 1,533 |
| 集中 | 256 | D1 | 3,273 | 71.0% | 38.9% | 49 | 94 | 86 |
| 集中 | 256 | R1 | 56 | 4.3% | 27.7% | 92 | 11,218 | 239 |
| 集中 | 256 | A1 | 39 | 3.9% | 30.6% | 112 | 13,286 | 325 |
| 集中 | 256 | A2 | 24 | 1.3% | 38.6% | 86 | 16,537 | 322 |
READ COMMITTED · 再試行最大3回
| 分布 | 同時実行数 | 構成 | 成功TPS | 生の競合率 | 最終失敗率 | p50 (ms) | p99 全体 (ms) | p99 コミット成功 (ms) |
|---|---|---|---|---|---|---|---|---|
| 均等 | 16 | R1 | 1,488 | 0.0% | 0.0% | 10 | 25 | 25 |
| 均等 | 16 | A1 | 872 | 0.0% | 0.0% | 17 | 42 | 42 |
| 均等 | 16 | A2 | 1,003 | 0.0% | 0.0% | 10 | 61 | 61 |
| 均等 | 64 | R1 | 1,893 | 0.0% | 0.0% | 32 | 86 | 86 |
| 均等 | 64 | A1 | 1,173 | 0.0% | 0.0% | 53 | 119 | 119 |
| 均等 | 64 | A2 | 985 | 0.0% | 0.0% | 68 | 181 | 181 |
| 均等 | 256 | R1 | 1,543 | 0.0% | 0.0% | 160 | 355 | 355 |
| 均等 | 256 | A1 | 1,213 | 0.0% | 0.0% | 203 | 442 | 442 |
| 均等 | 256 | A2 | 595 | 0.0% | 0.0% | 425 | 852 | 852 |
| 集中 | 16 | R1 | 177 | 0.6% | 1.1% | 11 | 2,129 | 1,040 |
| 集中 | 16 | A1 | 236 | 0.5% | 0.9% | 16 | 1,269 | 1,040 |
| 集中 | 16 | A2 | 152 | 0.7% | 0.9% | 10 | 2,006 | 1,728 |
| 集中 | 64 | R1 | 60 | 2.3% | 18.1% | 41 | 4,491 | 1,798 |
| 集中 | 64 | A1 | 61 | 2.7% | 23.3% | 52 | 3,868 | 1,890 |
| 集中 | 64 | A2 | 52 | 2.5% | 24.5% | 62 | 4,026 | 1,834 |
| 集中 | 256 | R1 | 60 | 0.0% | 28.5% | 92 | 11,674 | 199 |
| 集中 | 256 | A1 | 38 | 0.0% | 31.0% | 100 | 13,553 | 318 |
| 集中 | 256 | A2 | 0 | 0.0% | 100.0% | 9,286 | 9,693 | - |
観測のまとめ
- 整合性: 66個のセルすべてで不変条件の違反は0件でした。検査した不変条件は、在庫が負にならないこと、在庫の変動と販売数量の一致、残高総額の保存、業務IDごとに効果が1回であること、部分的な反映がないこと、確認されたコミットの消失がないことです。4サービスとも、この実装(条件付き更新、レシート、再試行、コミット有無の確認)で業務不変条件を守りました。
- 均等分布: 生の競合率はサービスに関係なく、同時実行数16で約0.5%、64で約2–3%、256で約8–10%と同程度でした(A2の同時実行数256は後述のとおり除外)。4サービスともREPEATABLE READで同じ行の競合を拒否するためです。最大3回の再試行を適用すると、最終失敗率は0–2.3%に下がりました。最も高い2.3%はA1の同時実行数256で、2秒の期限に達した振替によるものです。
- 集中分布でのD1:
- 生の競合率は同時実行数16/64/256で21%、44%、67%(再試行なし)に上がりました。
- 最大3回の再試行で最終失敗率は4.9%、18%、39%に減りましたが、0にはなりませんでした。
- ロック待機がないため、コミットに成功したリクエストのp99は110 ms以下でした。
- 集中分布での対照群:
- 行ロックの待ち行列ができました。R1の同時実行数256では、ロック待機中のセッションが最大206個ありました。
- REPEATABLE READで同時実行数64と256のときは、2秒の期限に達する振替が増え、成功TPSが24–512に落ち、最終失敗率が18–63%になりました。
- READ COMMITTEDでは競合エラーはほとんどありませんでしたが、同じロック待機のため、同時実行数64/256の最終失敗率は18–31%でした(A2の同時実行数256を除く)。
- A2の同時実行数256のセル2個: 成功は0件でした(REPEATABLE READ・均等・再試行なし、READ COMMITTED・集中)。失敗理由はすべて接続タイムアウト(
ConnectionTimeout)でした。ウォームアップが5秒しかなく、ACUが低い状態で新しい接続256個が一度に集中した結果です。この結果だけではA2の容量の限界なのか短いウォームアップのためなのかを区別できないため、結論には使いません。 - スループットを比較しない理由: 均等分布・同時実行数256で、D1は約8,500 TPSを出しました。対照群は
t4g.medium(バースト可能 2 vCPU)であるため約1,200–1,500 TPSでした。この差はサービスの比較ではなく縮小構成によるものであるため、スループットの倍率としては解釈しません。 計画規模での容量比較はE002で行います。
開発・運用のしやすさ
- 準備時間(作成リクエストから使用可能になるまで、1回測定):D1 42秒、A1 5分30秒、A2 6分37秒、R1 12分8秒(Multi-AZ)。
- 削除時間(実行終了から削除確認まで):D1 約2分、R1 約3分、A1 約11分、A2 約11分。
- DSQLの行変更制限の影響: DSQLはトランザクションあたりの変更行数が3,000行に制限されています(公式クォータ、2026-09-26確認)。そのため、セル間の初期化(前のセルの注文・レシートの削除)を1,000行単位に分けて実行する必要がありました。
- その結果、D1はセルあたり約3–8分かかりました。セル12個で約58分です。
- 対照群は同じコードでセルあたり約1分でした。R1はセル18個で16分でした。
- 大量削除・整理作業がDSQLでは運用上の負担になるという観測です。
- DSQLで追加の対応が必要だったもの:
- トランザクションを開始した後に作成したテーブルは、そのトランザクションから見えませんでした(
42P01)。そのため、シナリオツールがテーブルを先に作成するよう修正しました。 SHOW max_connectionsが20を返しました。公式クォータはクラスターあたり接続1万個であるため、この値で同時実行数を制限しないようツールを修正しました。- DSQLのサーバーエラーが1回発生し、セルを再実行しました。
- トランザクションを開始した後に作成したテーブルは、そのトランザクションから見えませんでした(
- 再試行の実装量: REPEATABLE READでは4サービスとも再試行が必要で、同じ再試行コード(
load.pyのrun_op、コミット有無の確認を含め約60行)を使いました。一方、対照群を既定のREAD COMMITTEDで使うと競合エラーはほとんどなく、再試行なしでも動作します。DSQLはREAD COMMITTEDをサポートしないため、再試行の実装は必須です。実装にかけた作業時間は測定していません。
費用
単価は2026-09-25–26にAWS Price List APIで確認したソウルリージョンのOn-Demand価格(USD)です。DSQLは100万DPUあたりUSD 10(2026-09-11掲載版)です。下の表の推定費用は実行当時の使用量と単価から計算した値で、実際の請求額は2026-09-28にCost Explorer(ソウルリージョン、2026-09-25–26 UTC、使用タイプ別)で確認した値です。実験と無関係にアカウントで常時発生していた料金(ELB、S3、VPCなど)は除外しました。
| 項目 | 使用量 | 推定費用 | 実際の請求額 | 根拠と差異 |
|---|---|---|---|---|
| D1 DPU | 222,363 DPU(パイロット・シナリオ・初期化を含む) | 約$2.22 | $1.22 | 請求されたDPU(222,362)はCloudWatch TotalDPUの合計と一致しました。金額の差は月間無料使用量10万DPUが差し引かれたためです。 |
| R1 | 0.52時間 | 約$0.11 | $0.06 | 請求されたMulti-AZの使用時間は0.29時間で、作成リクエストから削除完了までを測った推定時間より短くなりました。 |
| A1 | 0.50時間 | 約$0.07 | $0.05 | 請求時間0.42時間。下記の料金率の説明を参照 |
| A2 | 1.91 ACU時間 | 約$0.50 | $0.39 | 請求1.86 ACU時間(CloudWatch ServerlessDatabaseCapacityの積分と3%の差)。下記の料金率の説明を参照 |
| 負荷発生器・ネットワーク | Spotランナー4台、合計約3.9時間 | 約$0.45 | 約$0.34 | 請求されたSpot使用2.77時間($0.28)、EBS・AZ間転送・パブリックIPv4 約$0.06 |
| 合計 | 約$3.4 | 約$2.07 | 推定の約61% |
- 推定が実際より高かった理由: 金額の差約$1.3のうち、約$1.0はDSQLの月間無料使用量によるものです。残りは、推定に使った稼働時間(作成リクエストから削除完了まで)が実際の課金時間より長かったためです。今月のDSQL無料使用量はこの実験ですべて使いました。
-
I/O-Optimizedの料金率: CloudTrailの記録上、A1とA2のクラスターは
aurora-iopt1(I/O-Optimized)で作成され、その後変更されていません。ところが請求記録では、A1の全体とA2の1.54 ACU時間がStandardの使用タイプと料金率($0.113/時間、$0.20/ACU時間)で記録され、I/O-Optimizedの料金率で請求されたのはA2の0.32 ACU時間だけです。Standardであれば発生するはずのI/O料金は請求されていません。構成はI/O-Optimizedで間違いなく、請求の分類がこうなった原因は確認できていません(照会時点のCost Explorerの値は推定状態であり、月次締めの際に変わる可能性があります)。 - DSQLのDPU消費量: D1のパイロットで、試行1回あたり約0.095 DPUでした。ソウルの単価で換算すると、試行100万回あたり約USD 0.95です。
- 最大スループット負荷の費用: DSQLはスループットが高いため、休みなくリクエストを送るclosed-loop負荷では、短時間でもDPU費用が急速に増えました。
- 単位費用を比較しない理由: 対照群は稼働時間を基準に課金され、今回のセルは25秒と短いものです。そのため、成功業務あたりの費用をサービス間で比較しても意味がありません。この比較はE010で同じ到着率により計算します。
結論と限界
- 整合性(問い1): 4サービスとも、競合と再試行の条件で業務不変条件を守りました。DSQLも再試行とコミット有無の確認を実装すれば、二重処理やコミットの消失なしに動作しました。
- 競合時の動作の違い(問い2):
- DSQLはロック待機なしにコミット時点で競合を通知します。そのため集中競合ではレイテンシは短く保たれますが、競合率が高く、再試行後も失敗が残ります(集中・同時実行数256で最終失敗率39%)。
- 対照群はロック待機で競合を吸収しようとするため、高競合では待機が期限を超え、スループットが急減しました。
- したがって、人気商品のように競合が集中する業務をDSQLへ移す際は、まず2点を検討する必要があります。再試行の予算(回数・期限)と、競合を分散するスキーマ設計です。たとえば在庫を複数の行に分ける方式があります。
- 実装量(問い3): DSQLにはREAD COMMITTEDがないため、再試行処理が必須です。対照群はREAD COMMITTEDを使えば再試行を省略できます。
- 限界:
- 反復が1回でセルが25秒と短いため、ばらつきを報告できません。測定区間にキャッシュ・ACUが安定する前の状態が混ざっている可能性があります。
- 対照群はバースト可能な小型構成であるため、スループットとレイテンシの絶対値は計画構成とは異なります。
- closed-loop負荷であるため、リクエスト間隔が開いている間のレイテンシが測定から漏れます(coordinated omission)。そのため、p99はその同時実行数での応答時間としてのみ解釈する必要があります。
- A2の同時実行数256の結果は、接続タイムアウトのため解釈しません。
- シナリオは分離レベルごとに1回の観測です。
- 外部の参考資料として、hot-keyスキーマでDSQLの失敗率が高かったという事例があります(Marc BowesのTPC-Bの記事)。今回の結果と同じ方向ですが、その数値は検証していません。
- 次の実験:
- E002:リクエスト率を固定するopen-loop方式で、SLO容量とレイテンシを計画規模で測定します。
- E003:接続の急増を扱います。A2の接続タイムアウト、DSQLの接続速度の制限を含みます。
後片付けの記録
- 作成したリソース:
- BATCH:VPC、サブネット2つ、IGW、セキュリティグループ2つ、DBサブネットグループ、IAMロール・インスタンスプロファイル、Spotランナー4台(パイロット用
c7g.2xlarge1台、c7g.4xlarge3台)。 - 構成別:D1クラスター、R1インスタンス、A1・A2クラスターとwriter、RDS管理のシークレット3つ。
- BATCH:VPC、サブネット2つ、IGW、セキュリティグループ2つ、DBサブネットグループ、IAMロール・インスタンスプロファイル、Spotランナー4台(パイロット用
- 削除完了時刻(UTC): D1 00:26:37、R1 00:58:04、A1 01:31:21、A2 02:07:38、BATCH 02:08:14。削除に失敗したリソースはありません。
- manifestになかったランナー1台: ランナー交換コードのバグにより、
c7g.4xlargeランナー1台(22:45:53 UTC作成)がmanifestに記録されないまま作成されました。このランナーは実験タグで所有を確認したうえで手動で終了し、22:48 UTCに終了が確認されました。同じ問題が再発しないようコードを修正しました。 - 検証(02:09:45 UTC):
e004.py verifyのremaining_count=0です。最初の検証(02:08:19)では、終了したランナーのSpotリクエスト1件がまだactive状態だったため1となり、リクエストがclosedに変わった後に再度検証しました。 - 手動のクロスチェック: DSQLクラスター0個、
e004接頭辞のRDSインスタンス・クラスター・クラスタースナップショット0個、IAMロールはNoSuchEntity、実験タグ付きのEC2インスタンス・ボリューム・ENI・Spotリクエスト(open/active)0個。