PostgreSQL 9.3リリース記念  FDWで本当のデッドロックにはまってみよう

2013年9月9日、いきなりPostgreSQL9.3がリリースされた。


それを記念してFDWを使って本当のデッドロック状態をつくって遊んでみようと思う。

準備

サーバを2台準備する。サーバはtest1とtest2とする。

test1

PostgreSQLとpostgres_fdwをインストールし、データベースtestdbを作る。

$ cd contrib/postgres_fdw
$ make && make install

$ cd /usr/local/pgsql
$ ./bin/createdb testdb

次に、CREATE EXTENSIONでpostgres_fdwを呼び込み、テスト用のテーブルaとbを作る。

testdb=# CREATE EXTENSION postgres_fdw;
CREATE EXTENSION
testdb=# CREATE TABLE a (id int);
CREATE TABLE
testdb=# CREATE TABLE b (id int);
CREATE TABLE
testdb=# INSERT INTO a VALUES(1);
INSERT 0 1
testdb=# INSERT INTO b VALUES(1);
INSERT 0 1

さらに先走って、test2のテーブルにアクセスするためのFDWの設定を行う。

testdb=# CREATE SERVER test2 FOREIGN DATA WRAPPER postgres_fdw
OPTIONS(host 'test2', dbname 'remotedb');
CREATE SERVER

testdb=# CREATE FOREIGN TABLE remote_a (id int) SERVER test2;
CREATE FOREIGN TABLE
testdb=# CREATE FOREIGN TABLE remote_b (id int) SERVER test2;
CREATE FOREIGN TABLE

testdb=# CREATE USER MAPPING FOR PUBLIC SERVER test2 OPTIONS(user 'postgres');
CREATE USER MAPPING
test2

こちらはデータベースremotedbを作る。

$ cd /usr/local/pgsql
$ ./bin/createdb remotedb

次に、テスト用のテーブルremote_aとremote_bを作る。

remotedb=# CREATE TABLE remote_a (id int);
CREATE TABLE
remotedb=# CREATE TABLE remote_b (id int);
CREATE TABLE
remotedb=# INSERT INTO remote_a VALUES(1);
INSERT 0 1
remotedb=# INSERT INTO remote_b VALUES(1);
INSERT 0 1

ローカルサーバ上でデッドロック

普通にデッドロック状態を作る。これはPostgreSQLデッドロックを検出して、すぐにデッロドック状態から抜け出てしまう。


[<トランザクションA>]
testdb=# BEGIN;
BEGIN
testdb=# UPDATE a SET id = 10;
UPDATE 1
testdb=# UPDATE b SET id = 10;
ERROR:  deadlock detected
DETAIL:  Process 28083 waits for ShareLock on transaction 1815; blocked by process 27977.
	Process 27977 waits for ShareLock on transaction 1816; blocked by process 28083.
	Process 28083: UPDATE a SET id = 20;
	Process 27977: UPDATE b SET id = 10;
HINT:  See server log for query details.
STATEMENT:  UPDATE a SET id = 20;
UPDATE 1


[<トランザクションB>]
testdb=# BEGIN;
BEGIN
testdb=# UPDATE b SET id = 20;
UPDATE 1
testdb=# UPDATE a SET id = 20;
ERROR:  deadlock detected
DETAIL:  Process 28083 waits for ShareLock on transaction 1815; blocked by process 27977.
Process 27977 waits for ShareLock on transaction 1816; blocked by process 28083.

リモートサーバ上でデッドロック

次にtest1からtest2のデータベースにアクセスして、デッドロック状態をつくてみる。

こちらも、test2のPostgreSQLデッドロックを検出して、すぐにデッドロック状態から抜け出てしまう。


[<トランザクションA>]
testdb=# BEGIN;
BEGIN
testdb=# UPDATE remote_a SET id = 10;
UPDATE 1
testdb=# UPDATE remote_b SET id = 10;
ERROR:  deadlock detected
DETAIL:  Process 31178 waits for ShareLock on transaction 1819; blocked by process 31130.
	Process 31130 waits for ShareLock on transaction 1820; blocked by process 31178.
HINT:  See server log for query details.
CONTEXT:  Remote SQL command: SELECT ctid FROM public.remote_a FOR UPDATE
STATEMENT:  UPDATE remote_a SET id = 20;
UPDATE 1


[<トランザクションB>]
testdb=# BEGIN;
BEGIN
testdb=# UPDATE remote_b SET id = 20;
UPDATE 1
testdb=# UPDATE remote_a SET id = 20;
ERROR:  deadlock detected
DETAIL:  Process 31178 waits for ShareLock on transaction 1819; blocked by process 31130.
Process 31130 waits for ShareLock on transaction 1820; blocked by process 31178.
HINT:  See server log for query details.
CONTEXT:  Remote SQL command: SELECT ctid FROM public.remote_a FOR UPDATE

ということで、DBのデッドロック検出機構は優秀なので、本当にデッドロック状態に陥って身動きできない状況を作るのは結構大変である。

デッドロックから脱出できないようにする


次、test1のテーブルとtest2のテーブルでデッドロック状態を作る。
この条件ではPostgreSQLデッドロックを検出できず、永続的なデッドロック状態を作り出すことができた。


[<トランザクションA>]
testdb=# BEGIN;
BEGIN
testdb=# UPDATE a SET id = 10;
UPDATE 1
testdb=# UPDATE remote_b SET id = 10;

..... 永遠に待ち状態 .....

[<トランザクションB>]
testdb=# BEGIN;
BEGIN
testdb=# 
testdb=# UPDATE remote_b SET id = 20;
UPDATE 1
testdb=# UPDATE a SET id = 20;

..... 永遠に待ち状態 .....

なぜこうなるかを簡単に。

test2側からみると、テーブルremote_bに対してtest1から2つの更新があったので、先に更新したclientBのトランザクションが終了するまで、後から更新したclientAをblockしているだけである。
まさかtest1側でclientBがclientAにブロックされているとは知り様もない。



念のためtest1側からみると、clientAはテーブルremote_bの更新のレスポンスがtest2から返ってこないでずっと待っている状態(FDWはただひたすら待つことしかできない)。
そしてclientBはClientAが先に更新したテーブルaのトランザクションが終了するのを待ってる。


ここまでの結論

FDWで更新処理ができるようになったのはスゴいことである。
しかしそれはまた、分散DBの技術的難題に直面したということでもある。

ここで書いたのは分散デッドロックの話で、解決策自体は存在している。問題はインプリメント
プライマリーサーバが主導してデッドロック検出するのはまだ簡単だが、いずれ下図に示すような複数台のサーバで分散クエリーを処理するシステムをつくる場合、デッドロック検出も分散化しなければならないかもしれない。



既にGreenPlumがPostgreSQLをベースに分散DBを作って商売しているわけですが、オープンソースで作れると面白いなあと。
少なくとも、スケーラビリティはPG-XCの比じゃないだろうと勝手に夢見ています。