PostgreSQL 9.1の同期レプリケーション:機構と新機能など

今さら、同期レプリケーションについて。ここでは機構のみ。新機能など含む全文はこちら
(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の使い方や新機能など含む全文はこちら