今さら、同期レプリケーションについて。ここでは機構のみ。新機能など含む全文はこちら
(2012.10.30追記)正式な文書は以下の書籍にまとめたので参照のこと。
また、http://www.interdb.jp/techinfo/postgresql/internal-11.html に原稿のサンプルをアップしたので、そちらを参照のこと。
先に結論を書くとPostgreSQLの同期レプリケーションは更新データ(WALログ)がスレーブ側のHDDに確実に書き込まれた後、マスタのトランザクションが閉じる。
つまり、トランザクションが正常に閉じた場合、 更新データは「マスタとスレーブのHDDに確実に書き込まれたと保証できる」 。
PostgreSQLの"同期レプリケーション"は 「レプリケーション=データ複製」という意味において完璧な機能を有している。
疑似コードと動作シーケンス
以下に、マスター側のプロセスがCommitを実行したときに呼ばれる関数RecordTransactionCommit()の概要、およびマスターとスレーブ間のシーケンス図を示す。
access/transam/xact.c RecordTransactionCommit(void) { /* * [1] Commit実行後、WALログをwrite()+flush()で確実にHDDに書き込む */ (void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata); XLogFlush(XactLastRecEnd); /* * [2] WALデータをスレーブに送信 */ WalSndWakeup(); TransactionIdCommitTree(xid, nchildren, children); /* CLOGの更新 */ /* * [3] スレーブからのACKを待つ(処理をblockする) */ SyncRepWaitForLSN(XactLastRecEnd) /* スレーブ側の処理 */ /* * [6] COMMIT処理を終了、トランザクションを閉じる */ }
[1]トランザクションがCommitするとRecordTransactionCommit()が呼び出され、XLogInsert()とXLogFlush()で、WALログデータが確実にHDDに書き込まれる。
[2]書き込まれたWALデータをスレーブに送信
[3]SyncRepWaitForLSN()がスレーブからのACKを待つ
スレーブにWALデータを送信したら、スレーブからのACKを待つ。
[4]受信したWALデータをwrite()する
ここからスレーブの処理である。システムコールwrite()でWALデータを書き込み、マスターにACKを返す。この時点では、WALデータがHDDに書き込まれたかどうかは不定である。
[5]flush()を実行する
flush()で確実にWALログデータをHDDに書き込み、またマスターにACKを返す。
[6]スレーブからのACKを受信したら、blockを解除してトランザクション終了
最後はまた、マスタ側の処理。
正確に言えば、ACKを受け取ったwalsenderが、もともとのトランザクションを発行したpostgresプロセスに「block解除せよ」とメッセージを送る。
非常に効率的なプロセス間通信が必要になるので、Unixのpipe機能をベースにしたPostgreSQL独自のLatch(pg_latch)を利用している。実装は「非常に苦労しただろうなあ、お疲れ様」という感じの力作。
機構に関連する話題3つ
落ち穂拾い的に。
ACKの構造
スレーブからのレスポンスは以下の疑似コードでわかるはず。
XLogWalRcvSendReply(void)@src/backend/replication/walreceiver.c /* Construct a new message */ reply_message.write = LogstreamResult.Write; reply_message.flush = LogstreamResult.Flush; reply_message.apply = GetXLogReplayRecPtr(); reply_message.sendTime = now; /* Prepend with the message type and send it. */ buf[0] = 'r'; memcpy(&buf[1], &reply_message, sizeof(StandbyReplyMessage)); walrcv_send(buf, sizeof(StandbyReplyMessage) + 1);
毎度、
- 書き込んだ(writeした)WALログのLSN
- flush()してHDDに書き込んだWALログのLSN
- HotStanbyでrecoveryプロセスが再生したWALログのLSN
- 現在時刻
を送っている。
WALログの送信タイミング
上の説明ではCOMMIT時のみのように誤解するかもしれないが、WALログ送信のトリガはマスタでのWALログ書き込みである。HDDに書き込んだ(flushした)ものを送る。
なので、COMMITだけでなく、WALバッファが溢れてHDDに書き込むとき、およびCHECKPOINTが走ってHDDに書き込むときもWALログはスレーブに送信される。
もちろん、WALログが送信されればACKが返る。
スレーブのHeartbeat
スレーブはHeartbeatとして周期的に信号を送っている。その信号はWALログのACKと同じものである。
つまり、マスタは全スレーブの状態を常に把握しているということ。別途紹介するが、その状態を表示するViewがある。
ということで、ACK信号=スレーブのレスポンスは、COMMIT時のWALログ書き込みだけでなく他のタイミングでも届く。
マスタ側がCOMMIT時のレスポンスをみつけて、*確実に*当該のプロセスのblockを解除しトランザクションを閉じる、というコーディングは面倒ですよね。「お疲れ様」とはこういう意味。
pg_basebackupの使い方や新機能など含む全文はこちら