postgre sql9.3 newlockmode_and_etc

29
PostgreSQL 9.3 の新しいロック モード - 共有ロックを振り返る - 2013.2.16 笠原 辰仁

Upload: kasaharatt

Post on 30-Jun-2015

571 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Postgre sql9.3 newlockmode_and_etc

PostgreSQL 9.3 の新しいロックモード - 共有ロックを振り返る -

2013.2.16笠原 辰仁

Page 2: Postgre sql9.3 newlockmode_and_etc

去る 2013.1.24ごろ● PostgreSQLのgitに

「Improve concurrency of foreign key locking」なるパッチが入る

● 何となく覗くと凄い大きい (106 files changed)– 実に2年以上かけてようやっと入った改良– 9.3で一番複雑なパッチではなかろうか?

● マテビューとかBgWorkerとかpostgresql_fdwなどは注目していたけど、これは何だ?

● というのがこの話をしようと思ったきっかけです

Page 3: Postgre sql9.3 newlockmode_and_etc

改良の内容● 簡潔に言うと1. 新しい行ロックモードが導入された

• 今までは FOR SHARE と FOR UPDATE の2つ• 今回新たに FOR KEY SHARE と

FOR NO KEY UPDATE が加わった2. 外部キーでこの新しいロックモードを使うことで、競合

が減って性能向上となる• 外部キーの参照テーブルへのINSERT時は、被参照テーブルの該

当キー保持列にFOR KEY SHAREが実施される• ユーザが被参照テーブルに対し、キーを変更しない更新処理を

行う場合に自動でFOR NO KEY UPDATEが使われる• FOR KEY SHARE と FOR NO KEY UPDATEは競合しない!

Page 4: Postgre sql9.3 newlockmode_and_etc

新しいロックモードFOR UPDATE FOR NO KEY

UPDATEFOR SHARE FOR KEY SHARE

FOR UPDATE × × × ×FOR NO KEYUPDATE × × ×FOR SHARE × ×FOR KEY SHARE ×

FOR NO KEY UPDATE は キー(外部キーとしての被参照)列の更新を行わない更新時に使われる。FOR KEY SHARE は、弱いロックで、対象列のキー列が変更されないことだけを保証したい場合に使われる。

Page 5: Postgre sql9.3 newlockmode_and_etc

分かりづらいので、昔を振り返りつつ● 簡単な仕組みのおさらい

– PostgreSQL は外部キーをトリガで実現している– 参照テーブル、被参照テーブルについて、

TRIGGER EACH ROWSを仕掛けておき、矛盾する更新処理は弾くようになっている

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

INSERT INTO ORDER VALUES (1, 4, now());

ITEM_ID の 4は無い!

RI_FKey_check_ins

制約違反!

ORDERテーブル ITEMテーブル

Page 6: Postgre sql9.3 newlockmode_and_etc

寄り道● PostgreSQLで外部キーを設定すると自動的にトリ

ガが付きますが、どんなトリガなのかはシステムカタログやsrc/backend/utils/adt/ri_triggers.cを見ると分かります。

=# SELECT proname, prosrc    FROM pg_proc p, pg_trigger t    WHERE p.oid = t.tgfoid AND t.tgrelid = '被参照テーブル'::regclass; proname | prosrc----------------------+---------------------- RI_FKey_noaction_del | RI_FKey_noaction_del RI_FKey_noaction_upd | RI_FKey_noaction_upd(2 rows)=# SELECT proname , prosrc FROM pg_proc p, pg_trigger t WHERE p.oid = t.tgfoid AND t.tgrelid = '参照テーブル'::regclass; proname | prosrc-------------------+------------------- RI_FKey_check_ins | RI_FKey_check_ins RI_FKey_check_upd | RI_FKey_check_upd(2 rows)

Page 7: Postgre sql9.3 newlockmode_and_etc

PG8.0までの問題● 参照テーブルへINSERTすると、被参照テーブル

の当該キーのレコードへFOR UPDATEでロックをかけていた– 被参照テーブルで、参照テーブルが必要とするレコー

ドが無くなる(キー更新やDELETE)と困るから

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

INSERT INTO ORDER

VALUES (1, 3, now());

ITEM_ID の 3の変更阻止RI_FKey_check_ins

DELETE FROM ITEM WHERE ITEM_ID = 3;

ブロック!

ORDERテーブル ITEMテーブル

Page 8: Postgre sql9.3 newlockmode_and_etc

PG8.0までの問題● そのため、デッドロックの温床になりやすかった

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

INSERT INTO ORDER

VALUES (1, 1, now());

INSERT INTO ORDER

VALUES (1, 2, now())

INSERT INTO ORDER

VALUES (1, 2, now());

INSERT INTO ORDER

VALUES (1, 1, now())

背後でデッドロック・・

Page 9: Postgre sql9.3 newlockmode_and_etc

そこで、PG8.1から共有ロック導入● 参照テーブルへのINSERT時には、被参照テーブルへ排他

ではなく共有ロックを取れるように改良– FOR SHARE ロックモードが導入され、被参照テーブルへの

ロックはこれを使うようになった– FOR SHARE 同士は競合しない!

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

INSERT INTO ORDER

VALUES (1, 1, now());

INSERT INTO ORDER

VALUES (1, 2, now())

INSERT INTO ORDER

VALUES (1, 2, now());

INSERT INTO ORDER

VALUES (1, 1, now())

共有ロック同士は競合しない!更新処理やFOR UPDATEとは

競合

Page 10: Postgre sql9.3 newlockmode_and_etc

共有ロックの簡単?な仕組み● 共有ロックは、レコードのヘッダに誰が共有ロックをしてい

るかを記録しておく形で実現– 各プロセスが保持しているロック情報をメモリに置くと大変な量に

なってしまうため– そのため、マルチトランザクションとそれに属する仲間たち(メンバー)みたいな形で実装されている

XMIN XMAX その他 データ部分

12345 12346 ABC

XMIN XMAX その他 データ部分

12345 -- ABC

XMIN XMAX その他 データ部分

12345 2 ABC

XID=12345が挿入

XID=12346がFOR SHARE取得

XID=12347もFOR SHARE取得

これがMultiXID。このメンバーにはXID=12346と12347が含まれる。

Page 11: Postgre sql9.3 newlockmode_and_etc

共有ロックの簡単?な仕組み● XMAXには、マルチトランザクションIDが記録される

– 実態は、pg_multixact/offsets ディレクトリ配下のどのファイルのどのエントリを見るべきかの情報(MultiXIDから、ファイルとエントリが一意に決まる)

– そこからさらにpg_multixact/members ディレクトリ配下のファイルとエントリへ繋がる– この中にマルチトランザクションIDが含む実XIDのメンバーが記録されており、これと共有バッファ上

(PGPROCとか)の情報を元に、ロック管理をしている

XMIN XMAX その他 データ部分

12345 2 ABC

$PGDATA/pg_multixact/offsets/0000 など

$PGDATA/pg_multixact/members/0000 など

MultiXID=2を元にoffset配下のファイルとエントリを覗く

offset配下のファイルの該当エントリの情報を元に、membersを見に行く

members配下のファイルに、MultiXID=2が含むXIDのメンバーが記録されている。

Page 12: Postgre sql9.3 newlockmode_and_etc

PG9.2までの問題?● とはいえ、被参照テーブルには相変わらず更

新を妨げるロックとなっている– キーを更新しない場合でも競合してしまう・・・

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

INSERT INTO ORDER

VALUES (1, 3, now());

ITEM_ID の 3の変更阻止RI_FKey_check_ins

UPDATE ITEM SET

STOCK = STOCK – 100WHERE ITEM_ID=3;

ブロック!

ORDERテーブル ITEMテーブル

キーは更新しないんで勘弁してくれー

Page 13: Postgre sql9.3 newlockmode_and_etc

PG9.2までの問題?● そこで新たに FOR KEY SHARE と FOR NO

KEY UPDATE モードが加わった

ORDER_ID ITEM_ID DELIVER

1 1 2012-01-01

2 2 2012-01-02

ITEM_ID NAME STOCK

1 ペン 60

2 紙 80

3 寒天 1000

3 寒天 900

INSERT INTO ORDER

VALUES (1, 3, now());

ITEM_ID の 3の変更阻止RI_FKey_check_ins

UPDATE ITEM SET

STOCK = STOCK – 100WHERE ITEM_ID=3;

キー変更しないならOK

ORDERテーブル ITEMテーブル

このときの更新処理では裏でNO KEY UPDATE モード

となっている

Page 14: Postgre sql9.3 newlockmode_and_etc

仕組み● ロックの管理については、基本的な仕組みは変

わっていない– 共有トランザクションの仕組みを使う– ただし、以前までは単純に共有ロックを確保している

メンバー情報だけだったが、加えてロックモード等のフラグ情報もmembers配下の情報に入っている

– pg_multixact 配下のファイルのクリーンナップは、VACUUM FREEZEのタイミング

=# SELECT * FROM pg_get_multixact_members('6'::xid); xid | mode------+------- 1006 | keysh 1010 | keysh

Page 15: Postgre sql9.3 newlockmode_and_etc

仕組み● 基本的な仕組みは変わっていない

– 更新時、キー列の更新の有無を確認するルーチンが追加されており、自動でロックモードを調整している

heap_update()(snip) HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, &satisfies_hot, &satisfies_key, &oldtup, newtup); if (satisfies_key) { *lockmode = LockTupleNoKeyExclusive; mxact_status = MultiXactStatusNoKeyUpdate; key_intact = true; MultiXactIdSetOldestMember(); } else { *lockmode = LockTupleExclusive; mxact_status = MultiXactStatusUpdate; key_intact = false; }(snip)

Page 16: Postgre sql9.3 newlockmode_and_etc

おわりに● 地味な新機能ですが、裏ではとても小難しい処理がなされて

います● 決して派手では無いですが、DBMSの重要部分であるロック

が改良されており、広く恩恵を受ける改善ではないでしょうか?

● こういう機能改善がある点もPostgreSQLの魅力ですね● 開発者に感謝しつつ、PostgreSQLを存分に活用していきた

いです

● 本機能のメイン開発者 Alvaro氏のCOMMIT時のコメント↓● http://www.postgresql.org/message-id/20130123183312.GG4

[email protected] seriously hope that no patch of mine ever becomes this monstruous again.

Page 17: Postgre sql9.3 newlockmode_and_etc

小ネタ - Hookで遊ぼう -

2013.2.16笠原 辰仁

Page 18: Postgre sql9.3 newlockmode_and_etc

Hook● PostgreSQLの内部にはHookがいくつか仕掛けら

れており、ユーザが外部から処理の制御を奪えるようになっています

● pg_stat_statementsやauto_explainなどは、Hookの仕組みを活用し、Executorの処理の前後で独自処理を挟み込んでいます。– pg_stat_statementsは、SQL情報をピックアップした

り、処理にかかった時間を独自に計っています● もしHookを使って遊んでみたい場合は

– 簡単なものならauto_explain

– 応用編ならpg_stat_statements– をそれぞれ参考にすると良いです

Page 19: Postgre sql9.3 newlockmode_and_etc

折角なので簡単に遊ぶ● PostgreSQL9.2からは emit_log_hook が入り

ました– これは、サーバログへの出力前に、仕掛けられて

おりログメッセージの構造体をユーザ側で自由に改変!することも可能です

– あるいは、独自のファイルへ書き出し、サーバログには書かないなんてことも出来ます

– あまり自由度はありませんが、本気で取り組むと色々有用なHookかもしれません

– 今回は、これでちょっと遊んでみましょう– ちなみにHookはたくさんあります。探してみてく

ださい。

Page 20: Postgre sql9.3 newlockmode_and_etc

ところでエラーメッセージの構造体って?

● src/include/utils/elog.htypedef struct ErrorData{ int elevel; /* error level */ bool output_to_server; /* will report to server log? */ bool output_to_client; /* will report to client? */ bool show_funcname; /* true to force funcname inclusion */ bool hide_stmt; /* true to prevent STATEMENT: inclusion */ const char *filename; /* __FILE__ of ereport() call */ int lineno; /* __LINE__ of ereport() call */ const char *funcname; /* __func__ of ereport() call */ const char *domain; /* message domain */ const char *context_domain; /* message domain for context message */ int sqlerrcode; /* encoded ERRSTATE */ char *message; /* primary error message */ char *detail; /* detail error message */ char *detail_log; /* detail error message for server log only */ char *hint; /* hint message */ char *context; /* context message */ char *schema_name; /* name of schema */ char *table_name; /* name of table */ char *column_name; /* name of column */ char *datatype_name; /* name of datatype */ char *constraint_name; /* name of constraint */ int cursorpos; /* cursor index into query string */ int internalpos; /* cursor index into internalquery */ char *internalquery; /* text of internally-generated query */ int saved_errno; /* errno at entry */} ErrorData;

Page 21: Postgre sql9.3 newlockmode_and_etc

ついでにエラーコード● src/backend/utils/errcodes.h/* Class 00 - Successful Completion */#define ERRCODE_SUCCESSFUL_COMPLETION MAKE_SQLSTATE('0','0','0','0','0')/* Class 01 - Warning */#define ERRCODE_WARNING MAKE_SQLSTATE('0','1','0','0','0')#define ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED MAKE_SQLSTATE('0','1','0','0','C')#define ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING MAKE_SQLSTATE('0','1','0','0','8')#define ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION MAKE_SQLSTATE('0','1','0','0','3')#define ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED MAKE_SQLSTATE('0','1','0','0','7')#define ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED MAKE_SQLSTATE('0','1','0','0','6')#define ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION MAKE_SQLSTATE('0','1','0','0','4')#define ERRCODE_WARNING_DEPRECATED_FEATURE MAKE_SQLSTATE('0','1','P','0','1')(以下略)

Page 22: Postgre sql9.3 newlockmode_and_etc

本当に簡単に使ってみる● auto_explainを元に改変してみた

– ユニーク制約エラー(23505)を99999にしてみよう

– edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION

だったら、– edata->sqlerrcode = MAKE_SQLSTATE('9','9','9','9','9')

にするだけ・・・

Page 23: Postgre sql9.3 newlockmode_and_etc

本当に簡単に使ってみる● auto_explainを元に改変してみた

– ユニーク制約エラー(23505)を99999にしてみよう

– edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION

だったら、– edata->sqlerrcode = MAKE_SQLSTATE('9','9','9','9','9')

にするだけ・・・

Page 24: Postgre sql9.3 newlockmode_and_etc

コード本体(log_hook_play)#include "postgres.h"#include "utils/elog.h"PG_MODULE_MAGIC;static emit_log_hook_type prev_emit_log_hook = NULL;void _PG_init(void);void _PG_fini(void);static void log_hook_play(ErrorData *edata);/* * Module load callback */void_PG_init(void){ EmitWarningsOnPlaceholders("log_hook_play"); prev_emit_log_hook = emit_log_hook; emit_log_hook = log_hook_play;}/* * Module unload callback */void_PG_fini(void){ /* Uninstall hooks. */ emit_log_hook = prev_emit_log_hook;}

必要なヘッダを読むPG_MODULE_MAGICをつける

先にいるHookの待避用

独自処理プロト

独自処理をロードした際の初期化処理。基本的にhookを仕掛けるのみ。共有バッファなどを間借りする場合はここでその処理をする

独自処理をアンロードした際のクリーンナップ処理。

Page 25: Postgre sql9.3 newlockmode_and_etc

コード本体(log_hook_play)/* * log_hook_play: Change SQL error code. */#define MY_ERRCODE_1 MAKE_SQLSTATE('9','9','9','9','9')static voidlog_hook_play(ErrorData *edata){ /* Check errcode and replace */ if (edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION) edata->sqlerrcode = MY_ERRCODE_1;}}

エラーメッセージの構造体の中身を見て、差し替える

Page 26: Postgre sql9.3 newlockmode_and_etc

MakefileMODULE_big = log_hook_playOBJS = log_hook_play.oifdef USE_PGXSPG_CONFIG = pg_configPGXS := $(shell $(PG_CONFIG) --pgxs)include $(PGXS)elsesubdir = contrib/log_hook_playtop_builddir = ../..include $(top_builddir)/src/Makefile.globalinclude $(top_srcdir)/contrib/contrib-global.mkendif

ContribモジュールのMakefileを参考に・・

EXTENSION対応はそれなりに難しいかもしれません。

Page 27: Postgre sql9.3 newlockmode_and_etc

使う● Make && make install したら、postgresql.confの

設定をする– Log関連

● logging_collector = on● log_line_prefix = '[%t][%p][%d][%u][%e]

– 大事なところ● shared_preload_libraries = 'log_hook_play'

– こうすると、PostgreSQLへの接続時に自動で共有ライブラリがロードされる

● もしくは、接続後に LOAD 'log_hook_play'; でもOK

Page 28: Postgre sql9.3 newlockmode_and_etc

試す

[2013-02-16 04:08:11 JST][72457][postgres][postgres][00000] LOG: 00000: statement: INSERT into test values(1);[2013-02-16 04:08:11 JST][72457][postgres][postgres][00000] LOCATION: exec_simple_query, postgres.c:890[2013-02-16 04:08:11 JST][72457][postgres][postgres][23505] ERROR: 23505: duplicate key value violates unique constraint "uqi"[2013-02-16 04:08:11 JST][72457][postgres][postgres][23505] DETAIL: Key (c1)=(1) already exists.[2013-02-16 04:08:11 JST][72457][postgres][postgres][23505] LOCATION: _bt_check_unique, nbtinsert.c:398[2013-02-16 04:08:11 JST][72457][postgres][postgres][23505] STATEMENT: INSERT into test values(1);

[2013-02-16 04:08:28 JST][72457][postgres][postgres][00000] LOG: 00000: statement: LOAD 'log_hook_play';[2013-02-16 04:08:28 JST][72457][postgres][postgres][00000] LOCATION: exec_simple_query, postgres.c:890[2013-02-16 04:08:31 JST][72457][postgres][postgres][00000] LOG: 00000: statement: INSERT into test values(1);[2013-02-16 04:08:31 JST][72457][postgres][postgres][00000] LOCATION: exec_simple_query, postgres.c:890[2013-02-16 04:08:31 JST][72457][postgres][postgres][99999] ERROR: 99999: duplicate key value violates unique constraint "uqi"[2013-02-16 04:08:31 JST][72457][postgres][postgres][99999] DETAIL: Key (c1)=(1) already exists.[2013-02-16 04:08:31 JST][72457][postgres][postgres][99999] LOCATION: _bt_check_unique, nbtinsert.c:398[2013-02-16 04:08:31 JST][72457][postgres][postgres][99999] STATEMENT: INSERT into test values(1);

エラーコード変わった

Page 29: Postgre sql9.3 newlockmode_and_etc

おわりに● お作法などは癖がありますが、分かってしまえば後は簡単?

● ただし、Hook処理は容易にPostgreSQLをクラッシュさせることもできるので、注意は必要です

● 内部の有意義な情報が簡単に扱えますので、トライしてみてはいかがでしょうか?