Postgresql - 原始碼 - background writer process
程式碼位置
src/backend/postmaster/bgwriter.c
background writer (bgwriter)是PG 8.0的新特性。檢視避免常規後端必須寫出髒共享緩衝區(只有在需要釋放共享緩衝區以在另一頁中讀取時才會這樣做。)最佳方案中,共享緩衝區的所有寫入將由後臺寫入器程序發出。但是,如果 bgwriter 無法保持足夠乾淨的緩衝區,則定期後端仍然有權發出寫入。
到PG 9.2中,bgwriter 不再處理 checkpoints。
一旦啟動子程序結束,postmaster 就啟動 bgwriter ,或者一旦恢復開始,如果我們正在進行存檔恢復。它仍然活著,直到 postmaster 命令它終止。正常終止是由SIGTERM ,會發出 exit(0)指示bgwriter 。SIGQUIT 是緊急終止;與任何後端一樣,bgwriter 將簡單地中止並退出SIGQUIT 。
如果bgwriter出乎意料地退出,postmaster 將處理該後端崩潰:共享記憶體可能損壞,因此剩餘的後端資料將被 SIGQUIT 殺死,然後開始恢復。
/* bgwriter process 主入口函式,這是從 AuxiliaryProcessMain 呼叫的,它已經建立了基本的執行環境,但還沒有啟用訊號。 */
void
BackgroundWriterMain(void)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext bgwriter_context;
bool prev_hibernate;
WritebackContext wb_context;
/* 正確地接受或忽略 postmaster 可能傳送給我們的訊號。 bgwriter 不參與ProcSignal 發訊號,但仍然需要鎖存喚醒的 SIGUSR1 處理程式。 */
pqsignal(SIGHUP, BgSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, bgwriter_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
/* 重置一些由 postmaster 接受但不在這裡接受的訊號 */
pqsignal(SIGCHLD, SIG_DFL);
pqsignal(SIGTTIN, SIG_DFL);
pqsignal(SIGTTOU, SIG_DFL);
pqsignal(SIGCONT, SIG_DFL);
pqsignal(SIGWINCH, SIG_DFL);
/* 允許在任何時候退出 SIGQUIT (quickdie) */
sigdelset(&BlockSig, SIGQUIT);
/* 建立一個 resource owner 來跟蹤我們的資源 (目前僅是buffer pins ) */
CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer");
/* 我們剛剛開始,假設有一個關閉或 end-of-recovery 快照 */
last_snapshot_ts = GetCurrentTimestamp();
/* 建立一個記憶體上下文,我們將完成所有的工作。我們這樣做,以便在錯誤恢復過程中可以重置上下文,從而避免可能的記憶體洩漏。以前,這段程式碼只是在 TopMemoryContext 執行,但是重新設定這將是一個非常糟糕的想法。 */
bgwriter_context = AllocSetContextCreate(TopMemoryContext,
"Background Writer",
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(bgwriter_context);
WritebackContextInit(&wb_context, &bgwriter_flush_after);
/* 如果遇到異常,則在此恢復處理 */
if (sigsetjmp(local_sigjmp_buf, 1) != 0)
{
/* 由於不使用PG_TRY,必須手動重置錯誤堆疊 */
error_context_stack = NULL;
/* 清理時防止中斷 */
HOLD_INTERRUPTS();
/* 向伺服器日誌報告錯誤 */
EmitErrorReport();
/* 這些操作實際上只是 AbortTransaction() 的最小子集。在 bgwriter中,我們沒有太多的資源需要擔心,但我們確實有LWLocks, buffers, 和 temp files。 */
LWLockReleaseAll();
ConditionVariableCancelSleep();
AbortBufferIO();
UnlockBuffers();
/* buffer pins 在這裡釋放: */
ResourceOwnerRelease(CurrentResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, true);
/* 我們不必擔心其他ResourceOwnerRelease 階段 */
AtEOXact_Buffers(false);
AtEOXact_SMgr();
AtEOXact_Files(false);
AtEOXact_HashTables(false);
/* 現在返回正常的頂級上下文,下次清除錯誤上下文。 */
MemoryContextSwitchTo(bgwriter_context);
FlushErrorState();
/* 在頂層上下文中清除任何洩漏的資料 */
MemoryContextResetAndDeleteChildren(bgwriter_context);
/* 重新初始化以避免重複錯誤引起問題 */
WritebackContextInit(&wb_context, &bgwriter_flush_after);
/* 現在我們可以再次中斷 */
RESUME_INTERRUPTS();
/* 在發生錯誤的之後至少sleep一秒。一個寫錯誤很可能會被重複,並且我們不想以儘可能快的速度填充錯誤日誌。 */
pg_usleep(1000000L);
/* 在所有錯誤之後關閉所有開啟的檔案。這在Windows上是有用的,在那裡儲存刪除的檔案會導致各種奇怪的錯誤。目前還不清楚我們需要在別處 */
smgrcloseall();
/* 當沒有進一步等待的可能性時,報告等待結束 */
pgstat_report_wait_end();
}
/* 現在我們可以處理ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
/* 解鎖訊號(當postmaster fork時,被鎖住) */
PG_SETMASK(&UnBlockSig);
/* 任何錯誤之後重置休眠狀態 */
prev_hibernate = false;
/* Loop forever */
for (;;)
{
bool can_hibernate;
int rc;
/* 清除任何已掛起的喚醒 */
ResetLatch(MyLatch);
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (shutdown_requested)
{
/* 從這裡開始,elog(ERROR) 應該以 exit(1) 結束,而不是將控制返回到上面的sigsetjmp 塊。 */
ExitOnAnyError = true;
/* 正常退出 */
proc_exit(0); /* done */
}
/* 做一個dirty-buffer 寫入迴圈。 */
can_hibernate = BgBufferSync(&wb_context);
/* 向stats collector 傳送活動統計資訊 */
pgstat_send_bgwriter();
if (FirstCallSinceLastCheckpoint())
{
/* 在任何檢查點之後,關閉所有 smgr 檔案。因此,我們不會無限期地掛起 smgr 對刪除檔案的引用。 */
smgrcloseall();
}
/* 記錄一個新的 xl_running_xacts 這樣複製可以更快地進入一致狀態(想想子溢位的快照),並且更頻繁地清理資源(鎖,KnownXids* )這樣做的成本相對較低,所以一分鐘做4次(LOG_SNAPSHOT_INTERVAL_MS)似乎很好。我們假設寫入 xl_running_xacts 的間隔比BgWriterDelay 大很多,因此我們不會使整個超時處理複雜化,而只是假設即使休眠模式是活動的,我們也會經常被呼叫。嚴格地說,log_snap_interval_ms介面是不重要的。為了確保我們在空閒系統上沒有不必要地喚醒磁碟,我們檢查自上次記錄執行的 xacts 以來是否插入了WAL。我們在 bgwriter 中進行日誌記錄,因為它是唯一有規律執行並始終返回到它的主迴圈的程序。例如,當啟用時,檢查指標幾乎不在主迴圈中,因此很難定期記錄。 */
if (XLogStandbyInfoActive() && !RecoveryInProgress())
{
TimestampTz timeout = 0;
TimestampTz now = GetCurrentTimestamp();
timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
LOG_SNAPSHOT_INTERVAL_MS);
/* 如果已經過了足夠的時間,並且自上次快照以來插入了有用的記錄,則只進行日誌記錄。<= 代替 < , 因為GetLastImportantRecPtr()指向記錄的開始,而last_snapshot_lsn 指向記錄的末尾。 */
if (now >= timeout &&
last_snapshot_lsn <= GetLastImportantRecPtr())
{
last_snapshot_lsn = LogStandbySnapshot();
last_snapshot_ts = now;
}
}
/* sleep,直到我們發出訊號或BgWriterDelay已經完成。BgBufferSync()中的反饋控制迴圈期望我們將呼叫它的每一毫秒BgWriterDelay。雖然這並不是正確的正確性,但如果我們偏離太遠,反饋迴路可能會出錯。因此,避免在正常操作期間可能頻繁發生的閂鎖事件載入該程序。 */
rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);
/* 如果沒有鎖存事件並且 BgBufferSync 表示什麼也沒有發生,那麼在“休眠”模式下擴充套件 sleep ,其中我們睡眠的時間比bgwriter_delay 更長。當後端再次使用緩衝區時,它會通過設定鎖存器來喚醒我們。因為只有當沒有緩衝區分配發生時才會持續額外的休眠,所以這不應該嚴重扭曲 BgBufferSync 的控制迴圈的行為;實際上,它會認為系統範圍的空閒間隔不存在。這裡存在一個競爭條件,後端可以在 BgBufferSync 將分配計數為零的時間和我們呼叫 StrategyNotifyBgWriter 的時間之間分配緩衝區。雖然我們無論如何不休眠並不重要,但我們試圖通過只在 BgBufferSync 表示連續兩個週期什麼都沒有發生時休眠來減少這種可能性。此外,我們用永遠不休眠減輕了錯過喚醒的任何可能的後果。 */
if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate)
{
/* 在分配下一個緩衝區請求通知 */
StrategyNotifyBgWriter(MyProc->pgprocno);
/* Sleep ... */
rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
BgWriterDelay * HIBERNATE_FACTOR,
WAIT_EVENT_BGWRITER_HIBERNATE);
/* 在我們超時的情況下重置通知請求 */
StrategyNotifyBgWriter(-1);
}
/* 如果 postmaster 程序死了,將緊急救助。這是為了避免對所有 postmaster 的子程序進行手工清理。 */
if (rc & WL_POSTMASTER_DEATH)
exit(1);
prev_hibernate = can_hibernate;
}
}