1. 程式人生 > >PostgreSQL如何刪除不使用的xlog文件

PostgreSQL如何刪除不使用的xlog文件

返回 while don pre 磁盤 4.3 2.3 文件信息 nag

一、問題
經常會在復制的時候遇到這樣的問題,需要復制的xlog文件找不到了。那麽xlog文件什麽時候刪除?又會刪除多少保留多少個xlog文件?都有哪些xlog文件需要保留?本文將從原理上對這些問題進行解讀。

二、原理
每次checkpoint後都會根據需要刪除或者回收不再需要的xlog文件。
1、首先估算兩次checkpoint之間產生的xlog量,根據這個量會計算出未來最大的日誌文件號從而回收不再需要的文件將其重命名為未來即將使用的日誌文件號:
1.1 UpdateCheckPointDistanceEstimate估算checkpoint之前產生的日誌量:
?? ?if (CheckPointDistanceEstimate < nbytes)//上次估算量比這次估算的小,則更新為這次的估算量

?? ??? ?CheckPointDistanceEstimate = nbytes;
?? ?else//否則,適當增加
?? ??? ?CheckPointDistanceEstimate =(0.90 CheckPointDistanceEstimate + 0.10 (double) nbytes);
2、計算上一次checkpoint時,所在的文件段號_logSegNo:
?? ?XLByteToSeg(PriorRedoPtr, _logSegNo);
3、計算需要保留的文件段號:從該段號_logSegNo開始的文件都不能被刪除,之前的需要刪除或回收:根據備機請求以及wal_keep_segments計算KeepLogSeg(recptr, &_logSegNo);
技術分享圖片

4、遍歷pg_wal目錄下的所有xlog文件,進行刪除:RemoveOldXlogFiles
4.1 跳過時間線進行比較,如果pg_wal目錄下的文件比_logSegNo小則被刪除或回收。那麽什麽條件下次被回收?
--RemoveXlogFile
4.2 計算回收文件重命名的未來最大文件段號recycleSegNo:
?? ?1)如果本次是第一次checkpoint,則未來最大段號recycleSegNo=當前段文件號+10
?? ?2)否則調用函數XLOGfileslop計算:
?? ??? ?2.1 估算下一次checkpoint結束時日誌位置:
?? ??? ??? ?distance=(2.0+checkpoint_completion_target)CheckPointDistanceEstimate

?? ??? ??? ?distance=1.1
?? ??? ??? ?recycleSegNo = (XLogSegNo) ceil(((double) PriorRedoPtr + distance) / XLOG_SEG_SIZE);
?? ??? ?2.2 minSegNo = PriorRedoPtr / XLOG_SEG_SIZE + ConvertToXSegs(min_wal_size_mb) - 1;
?? ??? ??? ?maxSegNo = PriorRedoPtr / XLOG_SEG_SIZE + ConvertToXSegs(max_wal_size_mb) - 1;
?? ??? ?2.3 if (recycleSegNo < minSegNo)
?? ??? ??? ??? ?recycleSegNo = minSegNo;
?? ??? ??? ?if (recycleSegNo > maxSegNo)
?? ??? ??? ??? ?recycleSegNo = maxSegNo;
4.3 如果當前段文件號endlogSegNo < recycleSegNo,則調用InstallXLogFileSegment進行回收:
?? ?1)在endlogSegNo和recycleSegNo之間找一個free slot num,即沒有該段文件號的xlog文件
?? ?2)將需要刪除的文件名命名為該free slot號的文件名
?? ?3)如果沒有找到free slot則直接刪除該文件
--RemoveXlogFile

三、代碼流程
1、checkpoint頂層函數CreateCheckPoint:


CreateCheckPoint:
    XLogCtlInsert *Insert = &XLogCtl->Insert;//標識插入的位置
    curInsert = XLogBytePosToRecPtr(Insert->CurrBytePos);//添加頁頭大小後的位置
    //(((curInsert) % XLOG_BLCKSZ == 0) ? 0 : (XLOG_BLCKSZ - (curInsert) % XLOG_BLCKSZ))
    freespace = INSERT_FREESPACE(curInsert);//curInsert所在頁是否有空閑空間
    if (freespace == 0){
        if (curInsert % XLogSegSize == 0)//正好一個xlog段文件用完,即將使用下一個段文件,則跳過36字節
            curInsert += SizeOfXLogLongPHD;//36字節
        else//xlog段文件中正好一頁用完,即將使用下一頁,則跳過20字節
            curInsert += SizeOfXLogShortPHD;//20字節
    }
    checkPoint.redo = curInsert;//xlog文件上,實際的即將插入位置
    RedoRecPtr = XLogCtl->Insert.RedoRecPtr = checkPoint.redo;
    ...
    //插入checkpoint記錄後末尾位置,即下一個xlog開始的位置
    recptr = XLogInsert(RM_XLOG_ID,shutdown ? XLOG_CHECKPOINT_SHUTDOWN :XLOG_CHECKPOINT_ONLINE);
    ...
    PriorRedoPtr = ControlFile->checkPointCopy.redo;//上一次checkpoint的起始位置
    ...
    if (PriorRedoPtr != InvalidXLogRecPtr){//上一次checkpoint開始到這一次checkpoint開始,產生的XLOG大小為入參
        /*
        CheckPointDistanceEstimate:
        1、CheckPointDistanceEstimate<RedoRecPtr - PriorRedoPtr時:RedoRecPtr - PriorRedoPtr
        2、CheckPointDistanceEstimate>=RedoRecPtr - PriorRedoPtr時:0.9*CheckPointDistanceEstimate+0.1*(RedoRecPtr - PriorRedoPtr)
        */
        UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);
        //_logSegNo = (PriorRedoPtr) / XLogSegSize
        XLByteToSeg(PriorRedoPtr, _logSegNo);
        KeepLogSeg(recptr, &_logSegNo);
        _logSegNo--;
        RemoveOldXlogFiles(_logSegNo, PriorRedoPtr, recptr);

2、兩個宏定義

#define UsableBytesInPage (XLOG_BLCKSZ - SizeOfXLogShortPHD)//註意:不是文件第一頁
#define UsableBytesInSegment ((XLOG_SEG_SIZE / XLOG_BLCKSZ) * UsableBytesInPage - (SizeOfXLogLongPHD - SizeOfXLogShortPHD))

3、函數XLogBytePosToRecPtr

static XLogRecPtr
XLogBytePosToRecPtr(uint64 bytepos)
{

    //bytepos:不包括xlog頁的頁頭等額外字節占用的大小
    fullsegs = bytepos / UsableBytesInSegment;
    bytesleft = bytepos % UsableBytesInSegment;
    /*
    1、如果bytesleft < XLOG_BLCKSZ-32,則表示定位到第一頁上,則文件偏移值跳過第一頁頁頭大小
    2、如果bytesleft >= XLOG_BLCKSZ-32,則表示定位不是第一頁
    */
    if (bytesleft < XLOG_BLCKSZ - SizeOfXLogLongPHD){
        /* fits on first page of segment */
        seg_offset = bytesleft + SizeOfXLogLongPHD;
    }else{
        /* account for the first page on segment with long header */
        seg_offset = XLOG_BLCKSZ;//先跳過第一頁
        bytesleft -= XLOG_BLCKSZ - SizeOfXLogLongPHD;//去掉第一頁存放XLOG的大小

        fullpages = bytesleft / UsableBytesInPage;//剩下的需要幾個頁
        bytesleft = bytesleft % UsableBytesInPage;//剩下的偏移
        //  文件偏移=第一頁大小+剩下的幾個頁大小+剩下的偏移+最後一頁的頁頭
        seg_offset += fullpages * XLOG_BLCKSZ + bytesleft + SizeOfXLogShortPHD;
    }
    //result=(fullsegs) * XLOG_SEG_SIZE + seg_offset
    XLogSegNoOffsetToRecPtr(fullsegs, seg_offset, result);
    return result;
}

4、函數KeepLogSeg

static void
KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo)
{
    //segno為當前xlog即將插入位置在第幾個文件上
    XLByteToSeg(recptr, segno);
    //XLogCtl->replicationSlotMinLSN;備機上請求預留的最小值?
    keep = XLogGetReplicationSlotMinimumLSN();
    /* compute limit for wal_keep_segments first */
    if (wal_keep_segments > 0){
        /* 
        首先計算wal_keep_segments得到的限制:
        1、比如wal_keep_segments值是10,若當前insert的位置的文件號segno為5,那麽向前推進到1
        2、否則向前推進wal_keep_segments後的segno前的可刪除
        */
        if (segno <= wal_keep_segments)
            segno = 1;
        else
            segno = segno - wal_keep_segments;
    }

    /* then check whether slots limit removal further */
    //計算slots限制,如果其算出的值小於wal_keep_segments計算出的值,則需要使用slotSegNo,slots還有用,不能刪除
    if (max_replication_slots > 0 && keep != InvalidXLogRecPtr){
        XLByteToSeg(keep, slotSegNo);
        if (slotSegNo <= 0)
            segno = 1;
        else if (slotSegNo < segno)
            segno = slotSegNo;
    }

    /* don‘t delete WAL segments newer than the calculated segment */
    if (segno < *logSegNo)
        *logSegNo = segno;
    //note:
    //如果計算出的segno比上次checkpoint時的文件號logSegNo還有小,則取這次計算的segno
    //如果計算出的segno比上次checkpoint時的文件號logSegNo大,則取上次checkpoint時的文件號。
    //因為恢復時如果是主機,讀取最新checkpoint記錄失敗後,會讀取上一次checkpoint記錄,如果上次checkpoint的文件被刪除,這裏就讀取不到記錄了
}
5、函數RemoveOldXlogFiles

static void
RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr PriorRedoPtr, XLogRecPtr endptr)
{
//首先獲取xlog目錄
xldir = AllocateDir(XLOGDIR);
if (xldir == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open write-ahead log directory \"%s\": %m",
XLOGDIR)));

/*
 構建一個log文件名,用於判斷,該文件之前的xlog可以刪除。用不到時間線,所以可以使用0
 */
XLogFileName(lastoff, 0, segno);

while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL){
    /* 忽略非xlog文件 */
    if (!IsXLogFileName(xlde->d_name) &&
        !IsPartialXLogFileName(xlde->d_name))
        continue;
    /*
    1、跳過時間線進行比較
     */
    if (strcmp(xlde->d_name + 8, lastoff + 8) <= 0){
        if (XLogArchiveCheckDone(xlde->d_name)){//如果沒有開啟歸檔:總是TRUE;否則,歸檔完成後才為TRUE
            /* Update the last removed location in shared memory first */
            //XLogCtl->lastRemovedSegNo = segno;
            UpdateLastRemovedPtr(xlde->d_name);
            RemoveXlogFile(xlde->d_name, PriorRedoPtr, endptr);
        }
    }
}

}

6、函數RemoveXlogFile

RemoveXlogFile(const char *segname, XLogRecPtr PriorRedoPtr, XLogRecPtr endptr)
{

XLByteToSeg(endptr, endlogSegNo);
if (PriorRedoPtr == InvalidXLogRecPtr)
    recycleSegNo = endlogSegNo + 10;
else
    recycleSegNo = XLOGfileslop(PriorRedoPtr);

snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);

if (endlogSegNo <= recycleSegNo &&
    lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
    InstallXLogFileSegment(&endlogSegNo, path,
                           true, recycleSegNo, true))
{
    endlogSegNo++;
}else{
    rc = durable_unlink(path, LOG);
}

}

7、函數InstallXLogFileSegment

static bool
InstallXLogFileSegment(XLogSegNo segno, char tmppath,
bool find_free, XLogSegNo max_segno,
bool use_lock)
{
XLogFilePath(path, ThisTimeLineID, segno);
/

  • We want to be sure that only one process does this at a time.
    */
    if (use_lock)
    LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);

    if (!find_free)
    {
    / Force installation: get rid of any pre-existing segment file /
    durable_unlink(path, DEBUG1);//刪除文件並持久化到磁盤
    }else{
    / Find a free slot to put it in /
    while (stat(path, &stat_buf) == 0){//獲取文件信息並保存到stat_buf中,成功返回0
    //在segno和max_segno之間找一個空閑的段號,即目錄中沒有這個段號的xlog文件
    if ((segno) >= max_segno){
    /
    Failed to find a free slot within specified range /
    if (use_lock)
    LWLockRelease(ControlFileLock);
    return false;
    }
    (
    segno)++;
    XLogFilePath(path, ThisTimeLineID, segno);
    }
    }
    if (durable_link_or_rename(tmppath, path, LOG) != 0){//將tmppath重命名為path並持久化
    if (use_lock)
    LWLockRelease(ControlFileLock);
    /
    durable_link_or_rename already emitted log message */
    return false;
    }
    if (use_lock)
    LWLockRelease(ControlFileLock);
    return true;
    }

PostgreSQL如何刪除不使用的xlog文件