はじめに
最近、業務でスタンバイでのarchiverの挙動について話題になっていたので、自分でも調べてみました。
確認対象はPostgreSQL 10.0です。(なぜ10.1にしなかった。。。)
この記事で書いているのは
- archiverの概要
- archive_mode = alwaysでの「対象ファイル」と「実行契機」の確認
まずは基本。マニュアルに記載されている内容の確認
- WALのarchiveについて、色々と書かれているので一度は目を通しておきましょう。
-
archive_mode
- 各モードについての話が書かれている。
- alwaysモードのとき、アーカイブからリストアされたファイル、streamingされたファイルをすべてアーカイブする。
- 各モードについての話が書かれている。
- Continuous archiving in standby
-
archive_mode
特に注目なのは、「In always mode, all files restored from the archive or streamed with streaming replication will be archived (again). 」 という部分です。
今回はこの処理(リストアされたアーカイブWALをもう一度アーカイブする)をソースで確認してみようと思います。
archiverの概要
まずは簡単にarchiverプロセスについて紹介。
- archive_modeを有効にしている場合、PostgreSQL起動時に子プロセスとしてforkされるプロセス
- スタンバイで有効にする場合には「always」を設定する。
- pgarch_start() -> PgArchiverMain() -> pgarch_MainLoop() -> WaitLatch()と呼ばれていき、プロセス起動後は待機している。
- archiveの実行契機は以下のいずれか
- 時間契機(PGARCH_AUTOWAKE_INTERVAL デフォルト:60秒)
- PostmasterからのSIGNAL(PMSIGNAL_WAKEN_ARCHIVER)を受け取る
- archiveの実行対象
- archive_statusで.readyとなっているWAL
本題
ここからは、リストアされたアーカイブWALの.readyファイルがいつ作成されているのかを確認していきます。
そもそも.readyファイルはどこで作成されているか
アーカイブ対象を決める.readyファイルの作成自体はココでやっています。
511 void
512 XLogArchiveNotify(const char *xlog)
513 {
...
517 /* insert an otherwise empty file called <XLOG>.ready */
518 StatusFilePath(archiveStatusPath, xlog, ".ready");
519 fd = AllocateFile(archiveStatusPath, "w");
...
537 /* Notify archiver that it's got something to do */
538 if (IsUnderPostmaster)
539 SendPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER);
- XLogArchiveNotify関数でやっているのは
- .readyファイルの作成
- archiverプロセスにarchive処理を行うようにSIGNALを送る
つまり、この関数が呼ばれたら対象のWALの.readyが作成され、そのままarchiverプロセスにarchiveをリクエストしている。
XLogArchiveNotify関数の呼び出し元
呼び出し元の関数を追っていくとこんな感じでした。
XLogArchiveNotify() <--- 1. XLogArchiveNotifySeg() <--- XLogWrite() <--- 1-1. XLogFlush()
| |- 1-2. XLogBackgroundFlush()
| |- 1-3. AdvanceXLInsertBuffer()
|<--- 2. StartupXLOG()
| |- XLogPageRead()経由で呼ぶ
| |- 直接呼ぶ
|
|<--- 3. WalReceiverMain()
これだと何かわからないので、もう少し書いてみるとこんな感じです。(大体あっているはず。間違えていたらご指摘いただけると幸いです汗)
- WALファイルがwrite/flushされていき、ファイルの終端に到達して新しいファイルに切り替わったとき
- Startupプロセスからいくつかのパターンで呼んでいる。
- 起動時のrestore_command実行後に取得したファイルをアーカイブしている。★ココでやっている!!
-
ArchiveRecoveryRequested
が有効のとき(recovery.confが存在し、PITRさせた時)に呼んでいる-
writeTimeLineHistory()
で.historyファイルを作成し、即座に.historyファイルをアーカイブしている - 古いTLIのWALを、
XLogArchiveNotify(partialfname);
でpartialファイルとしてアーカイブしている。
-
- walreceiverプロセスがいくつかのタイミングで呼んでいる
- streamingのループ内で
XLogWalRcvProcessMsg()
でWALレコードを受け取って、次のセグメントファイルに到達したとき-
XLogWalRcvWrite()
でディスクに書くときにWALの位置が現在開いているファイル内に収まっているか確認している。- セグメントファイルに入っていない場合は
XLogArchiveNotify(xlogfname)
を呼んで、アーカイブして次のセグメントファイルに進む。
- セグメントファイルに入っていない場合は
-
- streamingのループを抜けた後、要求したTLIに到達したWALの最後を受け取ったとき
-
XLogArchiveNotify(xlogfname)
を呼んで、最後のWALをアーカイブしている。 - その後は、Startupプロセスから新しいTLI等が通知されるまで
WalRcvWaitForStartPosition()
で待機している。
-
- streamingのループ内で
該当のソースを紹介
上記の★部分が今回見たかったところですが、せっかくなので、★以外の部分も少し確認したので合わせて紹介します。
いつか、誰かがここらへんのソースを読むときの参考になれば幸いです。
XLogArchiveNotifySeg()
見たらわかる簡単なやつや!
XLogArchiveNotify()をwrappingしているだけです。
542 /*
543 * Convenience routine to notify using segment number representation of filename
544 */
545 void
546 XLogArchiveNotifySeg(XLogSegNo segno)
547 {
548 char xlog[MAXFNAMELEN];
549
550 XLogFileName(xlog, ThisTimeLineID, segno);
551 XLogArchiveNotify(xlog);
552 }
XLogArchiveNotifySeg()
は、WriteRqstを受け取ったWALをwriteかfsyncしているXlogWrite()
でのみ使用されています。
で、このWALの行っているのが以下の関数たちです。
- XLogFlush():指定された位置までXLOGをflushしている関数
- 色んな所から呼ばれているチェックポイントとかでも使われている。
- XLogBackgroundFlush():位置の指定はなく、xlogのwrite、flushを行っている関数
- walwriterプロセスから永続的に呼ばれている
- AdvanceXLInsertBuffer():WALバッファの初期化
- XlogBackgroudFlush()からも呼ばれている。
StartupXLOG()
名前からもなんとなくわかるStartup processのメインループをやっている関数。
6193 void
6194 StartupXLOG(void)
6195 {
...
6298 /*
6299 * Check for recovery control file, and if so set up state for offline
6300 * recovery
6301 */
6302 readRecoveryCommandFile(); // recovery.confが存在する場合ここでArchiveRecoveryRequestedがtrueになる。
...
6508 /*
6509 * Get the last valid checkpoint record. If the latest one according
6510 * to pg_control is broken, try the next-to-last one.
6511 */
6512 checkPointLoc = ControlFile->checkPoint;
6513 RedoStartLSN = ControlFile->checkPointCopy.redo;
6514 record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true); ★
...
7021 /*
7022 * main redo apply loop
7023 */
7024 do
7025 {
...
7233 /* Exit loop if we reached inclusive recovery target */
7234 if (recoveryStopsAfter(xlogreader))
7235 {
7236 reachedStopPoint = true;
7237 break;
7238 }
7239
7240 /* Else, try to fetch the next WAL record */
7241 record = ReadRecord(xlogreader, InvalidXLogRecPtr, LOG, false);
7242 } while (record != NULL);
7243
7244 /*
7245 * end of main redo apply loop
7246 */
...
7591 if (ArchiveRecoveryRequested)
7592 {
...
7633 if (EndOfLog % XLOG_SEG_SIZE != 0 && XLogArchivingActive())
7634 {
7635 char origfname[MAXFNAMELEN];
7636 XLogSegNo endLogSegNo;
7637
7638 XLByteToPrevSeg(EndOfLog, endLogSegNo);
7639 XLogFileName(origfname, EndOfLogTLI, endLogSegNo);
7640
7641 if (!XLogArchiveIsReadyOrDone(origfname))
7642 {
...
7658 XLogArchiveNotify(partialfname);★
今回見たかった、archiveからrestoreされたWALが再度archiveされているのは、6514 record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true);
でした。
何をやっているかというと、チェックポイントレコードを探すときにrestore_commandが呼んでおり、その後、取得したWALに対してXLogArchiveNotify()を実行しているというものでした。
StartupXLOG()
-> ReadCheckpointRecord()
-> ReadRecord()
-> XLogReadRecord()
-> ReadPageInternal()
-> XLogPageRead()
-> WaitForWALToBecomeAvailable()
-> XLogFileReadAnyTLI()
-> XLogFileRead()
-> KeepFileRestoredFromArchive()
-> XLogArchiveNotify()
なるほど、これでrestoreしたWALがもう一度archiveされているわけですね~。
<余談>
本題からは完全にそれますが( ・∀・)へ~ ってなったのは、ココ。
6302 readRecoveryCommandFile();
- recovery.confでPITRの設定等はこの関数内でparseされている。
- で、この関数内でArchiveRecoveryRequestedのフラグが有効になることで、以降のpartialファイルやhistoryファイルの作成が行われる。
- もちろんrecovery.confがなければ、ArchiveRecoveryRequestedはfalseのまま返却される。
ちなみにStartupプロセスのメインループを抜けるかどうかの判定はココにある。
7233 /* Exit loop if we reached inclusive recovery target */
7234 if (recoveryStopsAfter(xlogreader))
7235 {
7236 reachedStopPoint = true;
7237 break;
7238 }
- readRecoveryCommandFile()で読み込んだrecovery.confで設定した内容に到達したかどうかでループを抜けている。
- 抜けるのはどっちか。
- recovery_target_*が設定あり:その目的のポイントまでのリカバリが完了したときに抜ける
- recovery_target_*の設定なし:リカバリできるとこまでリカバリして、一貫性をもった状態になったときに抜ける
WalReceiverMain()
walreceiverのソースですね。
186 /* Main entry point for walreceiver process */
187 void
188 WalReceiverMain(void)
189 {
...
597 /*
598 * End of WAL reached on the requested timeline. Close the last
599 * segment, and await for new orders from the startup process.
600 */
601 if (recvFile >= 0)
602 {
...
616 XLogFileName(xlogfname, recvFileTLI, recvSegNo);
617 if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
618 XLogArchiveForceDone(xlogfname);
619 else
620 XLogArchiveNotify(xlogfname);★
621 }
- 該当の処理に入るのはstreamingのループを抜けた後(要求したTLIに到達したWALの最後を受け取ったとき)
- archive_mode = always のときは.readyファイルを作成する。
- always 以外のときはXLogArchiveForceDone()で、強制的に.doneを作成してアーカイブを完了ステータスにする。
- つまり、アーカイブしていないという話。
さいごに
なんかダラダラ書いてしまいましたが、とりあえずマニュアルで書いてあった再archiveしている処理を確認できたので良しとします。
アーカイブ周りって、なんとなくわかるけど実際どうなっているのかちゃんと読んだことがなかったので、良い機会になりました。