1. 程式人生 > >Postgresql - 原始碼 - background writer process

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;

    }

}