7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PostgreSQLAdvent Calendar 2024

Day 10

Aurora DSQL(プレビュー)で楽観的同時実行制御(OCC)を確認してみる

Last updated at Posted at 2024-12-09

これは PostgreSQL Advent Calendar 2024 2 枚目・10 日目の記事です。

5 日目に少しだけ試した Aurora DSQL(プレビュー版)は同時実行制御として楽観的同時実行制御(OCC)を採用していますが、この挙動を確認してみました。

悲観的同時実行制御(PCC)と楽観的同時実行制御(OCC)

通常の RDBMS では、悲観的同時実行制御(PCC : Pessimistic Concurrency Control)によるトランザクション処理を採用しています。

PCC では、ロックを使って書き込みの競合を管理します。

例えば、先行するトランザクションが行挿入を行う一方で後続のトランザクションが同一の主キー値で行挿入を行った場合、後続トランザクションは先行トランザクションがコミットまたはロールバックされるまで待たされます。そして先行トランザクションがコミットした場合は主キー重複エラーが発生、ロールバックした場合は行挿入が成功します。

一方、Aurora DSQL は楽観的同時実行制御(OCC : Optimistic Concurrency Control)を採用しています。

OCC では、トランザクションの途中ではロックを使わず処理を進め、コミットの段階で書き込み競合を判定します。

先ほどの例と同様に、先行するトランザクションが行挿入を行う一方で後続のトランザクションが同一の主キー値で行挿入を行った場合、先行トランザクションがコミットまたはロールバックするのを待つことなく後続トランザクションで行挿入が実行されます。そして、いずれかのトランザクションのうち、先にコミットした側が成功し、後からコミットした側が(変更の競合エラーで)失敗します。

Amazon Aurora DSQL の同時実行制御について、5 日目の記事には AWS 公式ブログの英語記事へのリンクを示しましたが、その後日本語訳が公開されたので、あらためて日本語ブログ記事へのリンクを示します。

まずは通常の PostgreSQL でトランザクションの動作を確認する

サーバーとして PostgreSQL 16 を使って試してみます。

今回は(動作がわかりやすいので)行挿入の動作を確認・比較します。更新の場合は違う動作になります。

テーブルを準備

まずスキーマとテーブルを準備します。

スキーマ・テーブル準備
postgres=# CREATE SCHEMA hoge;
CREATE SCHEMA
postgres=# CREATE TABLE hoge.fuga(id INT PRIMARY KEY UNIQUE, val INT);
CREATE TABLE

1 つ目のトランザクションで行挿入

引き続き、トランザクションを開始して主キーが1の行を挿入します。

トランザクション1で行挿入
postgres=# BEGIN;
BEGIN
postgres=*# INSERT INTO hoge.fuga VALUES(1, 100);
INSERT 0 1

2 つ目のトランザクションで行挿入

別のクライアント(psql)で 2 つ目のトランザクションを開始して、主キーが(重複するように)1の行を挿入します。

トランザクション2で行挿入
postgres=# BEGIN;
BEGIN
postgres=*# INSERT INTO hoge.fuga VALUES(1, 200);

ロック待ち状態になりました。

1 つ目のトランザクションでCOMMIT

1 つ目のトランザクションに戻ってCOMMITします。

トランザクション1でCOMMIT
postgres=*# COMMIT;
COMMIT

COMMITが成功しました。

そして、2 つ目のトランザクションでエラーが発生しました。

トランザクション2でエラー発生
ERROR:  duplicate key value violates unique constraint "fuga_pkey"
DETAIL:  Key (id)=(1) already exists.

2 つ目のトランザクションはエラーが出た状態で継続しているのでROLLBACKします。

トランザクション2でROLLBACK
postgres=!# ROLLBACK;
ROLLBACK

DSQL でトランザクションの動作を確認する

前回の記事のとおりに複数リージョンのクラスターを作成します。

(手順は省略)

テーブルを準備

先ほどと同様にスキーマとテーブルを準備します。

スキーマ・テーブル準備
postgres=# CREATE SCHEMA hoge;
CREATE SCHEMA
postgres=# CREATE TABLE hoge.fuga(id INT PRIMARY KEY UNIQUE, val INT);
CREATE TABLE

バージニア北部リージョンで 1 つ目のトランザクションを開始し行挿入

CloudShell でpsqlを使って DSQL クラスターのバージニア北部リージョンに接続します。

1 つ目のトランザクションを開始し主キーが1の行を挿入します。

バージニア北部リージョンで行挿入
postgres=> BEGIN;
BEGIN
postgres=*> INSERT INTO hoge.fuga VALUES(1, 100);
INSERT 0 1

ここまでは同じですね。

オハイオリージョンで 2 つ目のトランザクションを開始し行挿入

CloudShell でpsqlを使って DSQL クラスターのオハイオリージョンに接続します。

2 つ目のトランザクションを開始し主キーが(重複する)1の行を挿入します。

オハイオリージョンで行挿入
postgres=> BEGIN;
BEGIN
postgres=*> INSERT INTO hoge.fuga VALUES(1, 200);
INSERT 0 1

先ほどのようにロック待ちにはならずINSERTが実行されました。

バージニア北部リージョンでCOMMIT

バージニア北部リージョンでCOMMITしてみます。

バージニア北部リージョンでCOMMIT
postgres=*> COMMIT;
COMMIT

COMMITが成功しました。

この時点ではオハイオリージョンのトランザクションに変化は見られません。

オハイオリージョンでCOMMIT

オハイオリージョンでCOMMITしてみます。

オハイオリージョンでCOMMIT
postgres=*> COMMIT;
ERROR:  change conflicts with another transaction, please retry: (OC000)

この時点で初めてエラー(変更の競合)が出ました。

ロールバックは不要
postgres=> ROLLBACK;
WARNING:  there is no transaction in progress
ROLLBACK

プロンプトを見るとわかりますが、すでに 2 つ目のトランザクションは終了しているのでROLLBACKは不要です。

2 つのトランザクションを同一リージョンで実行しても同様の(OCC に沿った)動作になります。

これ以外のパターンは?

先の公式ブログ記事に、更新処理やSELECT ... FOR UPDATE、DDL を使うケースが例 1・例 2・例 3 として示されています。

変更の競合でエラーが発生したら?

エラーメッセージにもありますが、基本的にはエラーが発生した側のトランザクション処理をリトライ(最初からやり直し)する形で対処します。

現実には行挿入時に主キーが重複してリトライが発生するような設計のテーブルを扱うケースは少なく、ユニークキーや更新時の競合でリトライするケースが多いと思います。


明日(11 日目)の 2 枚目カレンダーの担当は jun2 さんです。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?