Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Postgre sql9.3 newlockmode_and_etc

  • Be the first to comment

  • Be the first to like this

Postgre sql9.3 newlockmode_and_etc

  1. 1. PostgreSQL 9.3 の新しいロック モード - 共有ロックを振り返る - 2013.2.16 笠原 辰仁
  2. 2. 去る 2013.1.24ごろ ● PostgreSQLのgitに 「Improve concurrency of foreign key locking」 なるパッチが入る ● 何となく覗くと凄い大きい (106 files changed) – 実に2年以上かけてようやっと入った改良 – 9.3で一番複雑なパッチではなかろうか? ● マテビューとかBgWorkerとかpostgresql_fdwなどは注目 していたけど、これは何だ? ● というのがこの話をしようと思ったきっかけです
  3. 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. 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. 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. 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. 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テーブル
  8. 8. 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()) 背後でデッドロック・・
  9. 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とは 競合
  10. 10. 共有ロックの簡単?な仕組み ● 共有ロックは、レコードのヘッダに誰が共有ロックをしてい るかを記録しておく形で実現 – 各プロセスが保持しているロック情報をメモリに置くと大変な量に なってしまうため – そのため、マルチトランザクションとそれに属する仲間たち(メン バー)みたいな形で実装されている 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が含まれる。
  11. 11. 共有ロックの簡単?な仕組み ● 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のメンバーが記録されている。
  12. 12. 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 – 100 WHERE ITEM_ID=3; ブロック! ORDERテーブル ITEMテーブル キーは更新しないんで 勘弁してくれー
  13. 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 モード となっている
  14. 14. 仕組み ● ロックの管理については、基本的な仕組みは変 わっていない – 共有トランザクションの仕組みを使う – ただし、以前までは単純に共有ロックを確保している メンバー情報だけだったが、加えてロックモード等の フラグ情報もmembers配下の情報に入っている – pg_multixact 配下のファイルのクリーンナップ は、VACUUM FREEZEのタイミング =# SELECT * FROM pg_get_multixact_members('6'::xid); xid | mode ------+------- 1006 | keysh 1010 | keysh
  15. 15. 仕組み ● 基本的な仕組みは変わっていない – 更新時、キー列の更新の有無を確認するルーチン が追加されており、自動でロックモードを調整し ている 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)
  16. 16. おわりに ● 地味な新機能ですが、裏ではとても小難しい処理がなされて います ● 決して派手では無いですが、DBMSの重要部分であるロック が改良されており、広く恩恵を受ける改善ではないでしょう か? ● こういう機能改善がある点もPostgreSQLの魅力ですね ● 開発者に感謝しつつ、PostgreSQLを存分に活用していきた いです ● 本機能のメイン開発者 Alvaro氏のCOMMIT時のコメント↓ ● http://www.postgresql.org/message-id/20130123183312.GG4 249@alvh.no-ip.org I seriously hope that no patch of mine ever becomes this monstruous again.
  17. 17. 小ネタ - Hookで遊ぼう - 2013.2.16 笠原 辰仁
  18. 18. Hook ● PostgreSQLの内部にはHookがいくつか仕掛けら れており、ユーザが外部から処理の制御を奪える ようになっています ● pg_stat_statementsやauto_explainなどは、Hook の仕組みを活用し、Executorの処理の前後で独 自処理を挟み込んでいます。 – pg_stat_statementsは、SQL情報をピックアップした り、処理にかかった時間を独自に計っています ● もしHookを使って遊んでみたい場合は – 簡単なものならauto_explain – 応用編ならpg_stat_statements – をそれぞれ参考にすると良いです
  19. 19. 折角なので簡単に遊ぶ ● PostgreSQL9.2からは emit_log_hook が入り ました – これは、サーバログへの出力前に、仕掛けられて おりログメッセージの構造体をユーザ側で自由に 改変!することも可能です – あるいは、独自のファイルへ書き出し、サーバロ グには書かないなんてことも出来ます – あまり自由度はありませんが、本気で取り組むと 色々有用なHookかもしれません – 今回は、これでちょっと遊んでみましょう – ちなみにHookはたくさんあります。探してみてく ださい。
  20. 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. 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') (以下略)
  22. 22. 本当に簡単に使ってみる ● auto_explainを元に改変してみた – ユニーク制約エラー(23505)を99999にしてみよう – edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION だったら、 – edata->sqlerrcode = MAKE_SQLSTATE('9','9','9','9','9') にするだけ・・・
  23. 23. 本当に簡単に使ってみる ● auto_explainを元に改変してみた – ユニーク制約エラー(23505)を99999にしてみよう – edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION だったら、 – edata->sqlerrcode = MAKE_SQLSTATE('9','9','9','9','9') にするだけ・・・
  24. 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. 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. 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. 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. 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); エラーコード変わった
  29. 29. おわりに ● お作法などは癖がありますが、分かってしま えば後は簡単? ● ただし、Hook処理は容易にPostgreSQLをク ラッシュさせることもできるので、注意は必 要です ● 内部の有意義な情報が簡単に扱えますので、 トライしてみてはいかがでしょうか?

×