More Related Content Similar to Postgre sql9.3 newlockmode_and_etc Similar to Postgre sql9.3 newlockmode_and_etc (20) Postgre sql9.3 newlockmode_and_etc2. 去る 2013.1.24ごろ
● PostgreSQLのgitに
「Improve concurrency of foreign key locking」
なるパッチが入る
● 何となく覗くと凄い大きい (106 files changed)
– 実に2年以上かけてようやっと入った改良
– 9.3で一番複雑なパッチではなかろうか?
● マテビューとかBgWorkerとかpostgresql_fdwなどは注目
していたけど、これは何だ?
●
というのがこの話をしようと思ったきっかけです
3. 改良の内容
●
簡潔に言うと
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は競合しない!
4. 新しいロックモード
FOR UPDATE FOR NO KEY
UPDATE
FOR SHARE FOR KEY SHARE
FOR UPDATE
× × × ×
FOR NO KEY
UPDATE × × ×
FOR SHARE
× ×
FOR KEY
SHARE ×
FOR NO KEY UPDATE は キー(外部キーとしての被参照)列の更新
を行わない更新時に使われる。
FOR KEY SHARE は、弱いロックで、対象列のキー列が変更され
ないことだけを保証したい場合に使われる。
5. 分かりづらいので、昔を振り返りつつ
●
簡単な仕組みのおさらい
– 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_in
s
制約違反!
ORDERテーブル ITEMテーブル
6. 寄り道
● 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)
7. 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テーブル
9. そこで、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とは
競合
13. 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 – 100
WHERE ITEM_ID=3;
キー変更しないならOK
ORDERテーブル ITEMテーブル
このときの更新処理では
裏でNO KEY UPDATE モード
となっている
19. 折角なので簡単に遊ぶ
● PostgreSQL9.2からは emit_log_hook が入り
ました
– これは、サーバログへの出力前に、仕掛けられて
おりログメッセージの構造体をユーザ側で自由に
改変!することも可能です
– あるいは、独自のファイルへ書き出し、サーバロ
グには書かないなんてことも出来ます
– あまり自由度はありませんが、本気で取り組むと
色々有用なHookかもしれません
– 今回は、これでちょっと遊んでみましょう
– ちなみにHookはたくさんあります。探してみてく
ださい。
20. ところでエラーメッセージの構造体っ
て?
● src/include/utils/elog.h
typedef 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;
21. ついでにエラーコード
● 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')
(以下略)
24. コード本体(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を仕掛けるのみ。
共有バッファなどを間借りする
場合はここでその処理をする
独自処理をアンロードした際の
クリーンナップ処理。
25. コード本体(log_hook_play)
/*
* log_hook_play: Change SQL error code.
*/
#define MY_ERRCODE_1 MAKE_SQLSTATE('9','9','9','9','9')
static void
log_hook_play(ErrorData *edata)
{
/* Check errcode and replace */
if (edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION)
edata->sqlerrcode = MY_ERRCODE_1;
}
}
エラーメッセージの構造体の中身を
見て、差し替える
26. Makefile
MODULE_big = log_hook_play
OBJS = log_hook_play.o
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/log_hook_play
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
ContribモジュールのMakefileを
参考に・・
EXTENSION対応はそれなりに
難しいかもしれません。
27. 使う
● 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
28. 試す
[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);
エラーコード変わった