- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- PostgreSQL/Pgpool-II/フェイルオーバー へ行く。
- 1 (2017-11-13 (月) 23:56:36)
- 2 (2017-11-13 (月) 23:57:45)
- 3 (2017-11-23 (木) 16:10:53)
- 4 (2017-11-23 (木) 23:34:19)
- 5 (2017-11-26 (日) 22:39:21)
- 6 (2017-11-27 (月) 00:17:33)
- 7 (2017-11-27 (月) 23:09:27)
- 8 (2017-11-29 (水) 01:26:39)
- 9 (2017-11-29 (水) 22:47:05)
- 10 (2017-12-02 (土) 21:06:08)
- 11 (2017-12-06 (水) 22:12:17)
- 12 (2017-12-08 (金) 22:47:05)
- 13 (2017-12-08 (金) 23:56:04)
- 14 (2017-12-09 (土) 11:43:37)
- 15 (2017-12-13 (水) 21:50:34)
- 16 (2017-12-15 (金) 01:14:08)
- 17 (2018-02-04 (日) 22:32:14)
ストリーミングレプリケーションモードを前提に記述する。
フェイルオーバー †
フェイルオーバーのタイミング †
フェイルオーバーの内部動作 †
- フェイルオーバー契機が与えられると、フェイルオーバーリクエストが共有メモリ上の待ち行列に書き込まれる。続いて、フェイルオーバー処理依頼のシグナル(SIGUSR1)が親プロセスに送信される。
参考register_node_operation_request() - on https://git.postgresql.org/gitweb/?p=pgpool2.git - フェイルオーバー処理はPgpoolの親プロセスで実行され、以下の関数で行われる。
参考failover() - on https://git.postgresql.org/gitweb/?p=pgpool2.git
フェイルオーバーの設定 †
fail_over_on_backend_error
またはhealth_check_period
の指定(1以上整数)が必要。
いずれも設定されていない状態で、プライマリノード障害が発生した場合、クライアントからの接続は成功せず以下のエラーとなる。
psql: FATAL: failed to create a backend 0 connection DETAIL: not executing failover because fail_over_on_backend_error is off
なお、health_checkは親プロセスからforkしたworkerプロセスで実施される。参考do_health_check_child()
health_checkがタイムアウトすると、degenerate_backend_set関数が呼ばれフェイルオーバーリクエストが待ち行列に追加され、フェイルオーバー処理を通知するシグナルがpgpoolの親プロセスに送信される。
フェイルオーバープロセス概要 †
failover()関数の処理概要は以下の通りである。(詳細は、ソースコードを参照のこと)
- 共有メモリの待ち行列にスイッチ中のフラグを立て、待ち行列のリクエスト処理ループに入る。
- 待ち行列のセマフォのロック。待ち行列にリクエストがなければ、アンロックして終了。
- 待ち行列からリクエストを取り出し、セフォマのアンロック。
- リクエストが「CLOSE_IDLE_REQUEST」ならば、すべての子プロセスにSIGUSR1シグナルを送信する。
(子プロセスは、SIGUSR1シグナルを受信するとアイドル状態のバックエンド接続を閉じる) - watchdogが有効ならば、watchdogにフェイルオーバー処理を通知する。
- 待ち行列のリクエストごとに以下の分岐
- NODE_UP_REQUESTの場合
・バックエンドが全てダウンしていないか調べる。
・バックエンドのステータスをCON_CONNECT_WAITにセットする。
・リクエストがREQ_DETAIL_UPDATEでなければfailback_commandを実行する。 - PROMOTE_NODE_REQUESTの場合
node_idがVALID_BACKENDでなければループをcontinueする。 - NODE_DOWN_REQUEST && NODE_QUARANTINE_REQUESTの場合
プライマリノードがダウンした場合など、このステップを通過する。NODE_QUARANTINE_REQUESTは、watchdogでクォーラムを組んでいる時のリクエスト種別である。ここで、障害でダウンしたデータベースノードを調べる。
- NODE_UP_REQUESTの場合
- 新しいマスタノードを決める。
- 待ち行列のリクエストごとに以下の分岐
- ストリーミングレプリケーションかつNODE_UP_REQUESTかつバックエンドが全てダウンしていない場合
need_to_restart_children = false、partial_restart = false
- ストリーミングレプリケーションかつ(NODE_DOWN_REQUESTまたはNODE_QUARANTINE_REQUEST)かつREQ_DETAIL_SWITCHOVERでプライマリノードでない場合
need_to_restart_children = true、partial_restart = true
- それ以外(プライマリノードダウンはここ)
すべての子プロセスにシグナルSIGQUITが送信される(すべての子プロセスは終了する)。子プロセス再起動のフラグを立てる。
need_to_restart_children = true、partial_restart = false
(ユーザーからのリクエストをacceptする子プロセスがいなくなる。)
- ストリーミングレプリケーションかつNODE_UP_REQUESTかつバックエンドが全てダウンしていない場合
- 待ち行列のリクエストがNODE_DOWN_REQUESTの場合、failover_commandに指定されたスクリプトを実行する。新しいプライマリノード候補のpromoteが実行される。
- 新しいプライマリノードを探索(pg_is_in_recovery()でチェック、探索時間はsearch_primary_node_timeoutパラメータ)
- ストリーミングレプリケーションモードの場合、follow_master_commandを実行する。この動作は、プロセスをforkして子プロセスで実行される。
- need_to_restart_childrenがtrueであれば、子プロセスの再起動を行なう。need_to_restart_childrenがfalseの場合は、リスタートフラグを立てるだけ。(子プロセス中でセッションが終了したタイミングで再起動される。)
参考check_restart_request() - on https://git.postgresql.org/gitweb/?p=pgpool2.git - workerにSIGUSR1シグナル(再起動通知)を送信する。
- sync_requiredがtrueならば、watchdogのフェイルオーバの終了処理。
- スイッチフラグを解除。
- pcpプロセスにSIGUSR2シグナルを送信し、failover/failbackの完了を通知。
- pcp再起動。
障害ケースとプロセス †
ケース:プライマリノード障害+fail_over_on_backend_error = onの場合 †
<テスト条件>
- プライマリノードダウン
pg_ctl -m i -D pgdata stop
- クライアント
- psqlで更新クエリを連続実行
- pgpool 3.7RC1
- num_init_children = 1
- fail_over_on_backend_error = on
- health_check_period = 0
- OS
- Mac OS X
- PostgreSQL
- 10.0
<実行>
連続クエリ実行中にプライマリノード障害を発生させ、親プロセスにシグナル送信される過程のスタックトレースが以下。
シグナル送信前にブレークポイントを設定している。
* thread #1: tid = 0x89d759, 0x0000000107d9ec63 pgpool`signal_user1_to_parent_with_reason(reason=SIG_FAILOVER_INTERRUPT) + 35 at pgpool_main.c:535, queue = 'com.apple.main-thread', stop reason = breakpoint 24.1 * frame #0: 0x0000000107d9ec63 pgpool`signal_user1_to_parent_with_reason(reason=SIG_FAILOVER_INTERRUPT) + 35 at pgpool_main.c:535 frame #1: 0x0000000107d9c4dc pgpool`register_node_operation_request(kind=NODE_DOWN_REQUEST, node_id_set=0x00007fff57e605c0, count=1, flags='\x01') + 460 at pgpool_main.c:508 frame #2: 0x0000000107d9f458 pgpool`degenerate_backend_set_ex(node_id_set=0x00007fff57e60824, count=1, flags='\x01', error='\0', test_only='\0') + 1720 at pgpool_main.c:1089 frame #3: 0x0000000107d9ed92 pgpool`degenerate_backend_set(node_id_set=0x00007fff57e60824, count=1, flags='\x01') + 50 at pgpool_main.c:1138 frame #4: 0x0000000107d9ed54 pgpool`notice_backend_error(node_id=0, flags='\x01') + 148 at pgpool_main.c:988 frame #5: 0x0000000107dd9ca8 pgpool`new_connection(p=0x00007fc8ec003248) + 792 at pool_connection_pool.c:873 frame #6: 0x0000000107dd94e1 pgpool`pool_create_cp + 225 at pool_connection_pool.c:248 frame #7: 0x0000000107dc9c1c pgpool`connect_backend(sp=0x00007fc8ed801f88, frontend=0x00007fc8ed800038) + 44 at child.c:868 frame #8: 0x0000000107dc6795 pgpool`get_backend_connection(frontend=0x00007fc8ed800038) + 981 at child.c:2326 frame #9: 0x0000000107dc465a pgpool`do_child(fds=0x00007fc8ebe00620) + 1898 at child.c:337 frame #10: 0x0000000107d9aac0 pgpool`fork_a_child(fds=0x00007fc8ebe00620, id=0) + 160 at pgpool_main.c:607 frame #11: 0x0000000107d979c6 pgpool`PgpoolMain(discard_status='\x01', clear_memcache_oidmaps='\0') + 1878 at pgpool_main.c:363 frame #12: 0x0000000107d95ea8 pgpool`main(argc=9, argv=0x00007fff57e6b7a8) + 2232 at main.c:318 frame #13: 0x00007fff920465ad libdyld.dylib`start + 1 frame #14: 0x00007fff920465ad libdyld.dylib`start + 1
notice_backend_error関数の実行後、子プロセスはabortして終了する。参考pool_connection_pool.c#new_connection()
pgpoolのログは以下のとおりである。
2017-11-23 23:11:07: pid 87593: [CHILD:269] LOG: DB node id: 0 backend pid: 87759 statement: insert into foo_t values(53); 2017-11-23 23:11:07: pid 87593: [CHILD:270] LOCATION: pool_proto_modules.c:3116 2017-11-23 23:11:07: pid 87593: [CHILD:271] LOG: DB node id: 0 backend pid: 87759 statement: DISCARD ALL 2017-11-23 23:11:07: pid 87593: [CHILD:272] LOCATION: pool_proto_modules.c:3116 2017-11-23 23:11:07: pid 87593: [CHILD:273] LOG: DB node id: 1 backend pid: 87760 statement: DISCARD ALL 2017-11-23 23:11:07: pid 87593: [CHILD:274] LOCATION: pool_proto_modules.c:3116 2017-11-23 23:11:07: pid 87593: [CHILD:275] LOG: connection closed. 2017-11-23 23:11:07: pid 87593: [CHILD:276] DETAIL: retry to create new connection pool 2017-11-23 23:11:07: pid 87593: [CHILD:277] LOCATION: pool_connection_pool.c:149 2017-11-23 23:11:07: pid 87593: [CHILD:278] LOG: failed to connect to PostgreSQL server by unix domain socket 2017-11-23 23:11:07: pid 87593: [CHILD:279] DETAIL: connect to "/tmp/.s.PGSQL.11002" failed with error "Undefined error: 0" 2017-11-23 23:11:07: pid 87593: [CHILD:280] LOCATION: pool_connection_pool.c:522 2017-11-23 23:11:07: pid 87593: [CHILD:281] LOG: received degenerate backend request for node_id: 0 from pid [87593] 2017-11-23 23:11:07: pid 87593: [CHILD:282] LOCATION: pgpool_main.c:1053 2017-11-23 23:11:07: pid 87593: [CHILD:283] FATAL: failed to create a backend connection 2017-11-23 23:11:07: pid 87593: [CHILD:284] DETAIL: executing failover on backend <== 子プロセスはabort 2017-11-23 23:11:07: pid 87593: [CHILD:285] LOCATION: pool_connection_pool.c:876 2017-11-23 23:11:07: pid 87587: [MAIN:17] LOG: Pgpool-II parent process has received failover request <== 親プロセスでfailover開始 2017-11-23 23:11:07: pid 87587: [MAIN:18] LOCATION: pgpool_main.c:1502 2017-11-23 23:11:07: pid 87587: [MAIN:19] LOG: starting degeneration. shutdown host /tmp(11002) 2017-11-23 23:11:07: pid 87587: [MAIN:20] LOCATION: pgpool_main.c:1714 2017-11-23 23:11:07: pid 87587: [MAIN:21] LOG: Restart all children <== 子プロセスは全てkillされる 2017-11-23 23:11:07: pid 87587: [MAIN:22] LOCATION: pgpool_main.c:1835 2017-11-23 23:11:07: pid 87587: [MAIN:23] LOG: execute command: /Users/guest/workspace/pgpool/test/etc/failover.sh 0 /tmp 11002 /Users/guest/workspace/pgpool/test/data0 1 0 /tmp 0 11003 /Users/guest/workspace/pgpool/test/data1 2017-11-23 23:11:07: pid 87587: [MAIN:24] LOCATION: pgpool_main.c:2795 2017-11-23 23:11:07: pid 87587: [MAIN:25] LOG: find_primary_node_repeatedly: waiting for finding a primary node 2017-11-23 23:11:07: pid 87587: [MAIN:26] LOCATION: pgpool_main.c:2967 2017-11-23 23:11:07: pid 87587: [MAIN:27] LOG: find_primary_node: checking backend no 0 2017-11-23 23:11:07: pid 87587: [MAIN:28] LOCATION: pgpool_main.c:2910 2017-11-23 23:11:07: pid 87587: [MAIN:29] LOG: find_primary_node: checking backend no 1 2017-11-23 23:11:07: pid 87587: [MAIN:30] LOCATION: pgpool_main.c:2910 2017-11-23 23:11:07: pid 87587: [MAIN:31] LOG: find_primary_node: primary node id is 1 2017-11-23 23:11:07: pid 87587: [MAIN:32] LOCATION: pgpool_main.c:2940 2017-11-23 23:11:07: pid 87587: [MAIN:33] LOG: starting follow degeneration. shutdown host /tmp(11002) 2017-11-23 23:11:07: pid 87587: [MAIN:34] LOCATION: pgpool_main.c:1928 2017-11-23 23:11:07: pid 87587: [MAIN:35] LOG: failover: 1 follow backends have been degenerated 2017-11-23 23:11:07: pid 87587: [MAIN:36] LOCATION: pgpool_main.c:1946 === follow_master_commandを実行するプロセスがforkされる === 2017-11-23 23:11:07: pid 87587: [MAIN:37] LOG: failover: set new primary node: 1 2017-11-23 23:11:07: pid 87587: [MAIN:38] LOCATION: pgpool_main.c:1961 2017-11-23 23:11:07: pid 87587: [MAIN:39] LOG: failover: set new master node: 1 2017-11-23 23:11:07: pid 87587: [MAIN:40] LOCATION: pgpool_main.c:1968 === ここで新しい子プロセスがforkされる === failover done. shutdown host /tmp(11002)2017-11-23 23:11:07: pid 87587: [MAIN:41] LOG: failover done. shutdown host /tmp(11002) 2017-11-23 23:11:07: pid 87587: [MAIN:42] LOCATION: pgpool_main.c:2074 2017-11-23 23:11:07: pid 87596: [WORKER:1] LOG: worker process received restart request 2017-11-23 23:11:07: pid 87596: [WORKER:2] LOCATION: pool_worker_child.c:155 2017-11-23 23:11:07: pid 88001: [UTILITY:1] LOG: start triggering follow command. <== follow_master_commandが実行される 2017-11-23 23:11:07: pid 88001: [UTILITY:2] LOCATION: pgpool_main.c:2995 2017-11-23 23:11:07: pid 88001: [UTILITY:3] LOG: execute command: /Users/guest/workspace/pgpool/test/etc/follow_master.sh 0 /tmp 11002 /Users/guest/workspace/pgpool/test/data0 1 0 /tmp 0 11003 /Users/guest/workspace/pgpool/test/data1 2017-11-23 23:11:07: pid 88001: [UTILITY:4] LOCATION: pgpool_main.c:2795 2017-11-23 23:11:07: pid 88002: [CHILD:1] LOG: DB node id: 1 backend pid: 88004 statement: insert into foo_t values(55); <== クエリのリトライ 2017-11-23 23:11:07: pid 88002: [CHILD:2] LOCATION: pool_proto_modules.c:3116 2017-11-23 23:11:07: pid 88002: [CHILD:3] LOG: DB node id: 1 backend pid: 88004 statement: DISCARD ALL 2017-11-23 23:11:07: pid 88002: [CHILD:4] LOCATION: pool_proto_modules.c:3116
メモ
- フェイルオーバーでプライマリノードが切り替わる前にクライアントからPgpoolへコネクション接続があった場合
- 子プロセスはfailover関数の早いうちにSIGQUITシグナルを受信しexitするため、Pgpoolのソケットに対するacceptは実行されない。したがってクライアントは子プロセスが再起動されるまで待ち状態となる。ただし、グローバルのバックエンドステータス更新前かつ子プロセスにSIGQUITが送信される前にクライアントからのPgpoolへのコネクション接続があった場合、子プロセスはバックエンドの接続に失敗しexitするためクライアントのPgpoolへのコネクション接続は失敗する。
グローバルのバックエンドステータスはノードダウンのリクエストが通知されるとfailover関数内でCON_DOWNに更新される。(ソース解析と動作からおそらくそうなる)
参考pool_connection_pool.c#new_connection()
仮に親プロセスのfailover処理が実行されずグルーバルのバックエンドステータスがCON_DOWNに更新されない場合、preforkされている子プロセスはクライアントからPgpoolへのコネクション接続があるたびにバックエンドへのコネクション接続で失敗しexitする。num_init_childrenで設定された子プロセスが全てexitするとacceptする子プロセスがいなくなり、クライアントのPgpoolへのコネクション接続は待ち状態となる(listenキューのバックログ)。
- 子プロセスはfailover関数の早いうちにSIGQUITシグナルを受信しexitするため、Pgpoolのソケットに対するacceptは実行されない。したがってクライアントは子プロセスが再起動されるまで待ち状態となる。ただし、グローバルのバックエンドステータス更新前かつ子プロセスにSIGQUITが送信される前にクライアントからのPgpoolへのコネクション接続があった場合、子プロセスはバックエンドの接続に失敗しexitするためクライアントのPgpoolへのコネクション接続は失敗する。