溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PostgreSQL中ReserveXLogInsertLocation和CopyXLogRecordToWAL函數(shù)的實現(xiàn)邏輯是什么

發(fā)布時間:2021-11-10 14:29:58 來源:億速云 閱讀:147 作者:iii 欄目:關(guān)系型數(shù)據(jù)庫

本篇內(nèi)容介紹了“PostgreSQL中ReserveXLogInsertLocation和CopyXLogRecordToWAL函數(shù)的實現(xiàn)邏輯是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

ReserveXLogInsertLocation函數(shù)為XLOG Record預留合適的空間,CopyXLogRecordToWAL則負責拷貝XLOG Record到WAL buffer的保留空間中。

一、數(shù)據(jù)結(jié)構(gòu)

全局變量

 /* flags for the in-progress insertion */ //用于插入過程中的標記信息 static uint8 curinsert_flags = 0;  /*  * These are used to hold the record header while constructing a record.  * 'hdr_scratch' is not a plain variable, but is palloc'd at initialization,  * because we want it to be MAXALIGNed and padding bytes zeroed.  * 在構(gòu)建XLOG Record時通常會存儲記錄的頭部信息.  * 'hdr_scratch'并不是一個普通(plain)變量,而是在初始化時通過palloc初始化,  *   因為我們希望該變量已經(jīng)是MAXALIGNed并且已被0x00填充.  *  * For simplicity, it's allocated large enough to hold the headers for any  * WAL record.  * 簡單起見,該變量預先會分配足夠大的空間用于存儲所有WAL Record的頭部信息.  */ static XLogRecData hdr_rdt; static char *hdr_scratch = NULL;  #define SizeOfXlogOrigin    (sizeof(RepOriginId) + sizeof(char))  #define HEADER_SCRATCH_SIZE \     (SizeOfXLogRecord + \      MaxSizeOfXLogRecordBlockHeader * (XLR_MAX_BLOCK_ID + 1) + \      SizeOfXLogRecordDataHeaderLong + SizeOfXlogOrigin) /*  * An array of XLogRecData structs, to hold registered data.  * XLogRecData結(jié)構(gòu)體數(shù)組,存儲已注冊的數(shù)據(jù).  */ static XLogRecData *rdatas; static int  num_rdatas;         /* entries currently used */ //已分配的空間大小 static int  max_rdatas;         /* allocated size */ //是否調(diào)用XLogBeginInsert函數(shù) static bool begininsert_called = false;  static XLogCtlData *XLogCtl = NULL;  /* flags for the in-progress insertion */ static uint8 curinsert_flags = 0;  /*  * A chain of XLogRecDatas to hold the "main data" of a WAL record, registered  * with XLogRegisterData(...).  * 存儲WAL Record "main data"的XLogRecDatas數(shù)據(jù)鏈  */ static XLogRecData *mainrdata_head; static XLogRecData *mainrdata_last = (XLogRecData *) &mainrdata_head; //鏈中某個位置的mainrdata大小 static uint32 mainrdata_len; /* total # of bytes in chain */  /*  * ProcLastRecPtr points to the start of the last XLOG record inserted by the  * current backend.  It is updated for all inserts.  XactLastRecEnd points to  * end+1 of the last record, and is reset when we end a top-level transaction,  * or start a new one; so it can be used to tell if the current transaction has  * created any XLOG records.  * ProcLastRecPtr指向當前后端插入的最后一條XLOG記錄的開頭。  * 它針對所有插入進行更新。  * XactLastRecEnd指向最后一條記錄的末尾位置 + 1,  *   并在結(jié)束頂級事務或啟動新事務時重置;  *   因此,它可以用來判斷當前事務是否創(chuàng)建了任何XLOG記錄。  *  * While in parallel mode, this may not be fully up to date.  When committing,  * a transaction can assume this covers all xlog records written either by the  * user backend or by any parallel worker which was present at any point during  * the transaction.  But when aborting, or when still in parallel mode, other  * parallel backends may have written WAL records at later LSNs than the value  * stored here.  The parallel leader advances its own copy, when necessary,  * in WaitForParallelWorkersToFinish.  * 在并行模式下,這可能不是完全是最新的。  * 在提交時,事務可以假定覆蓋了用戶后臺進程或在事務期間出現(xiàn)的并行worker進程的所有xlog記錄。  * 但是,當中止時,或者仍然處于并行模式時,其他并行后臺進程可能在較晚的LSNs中寫入了WAL記錄,  *   而不是存儲在這里的值。  * 當需要時,并行處理進程的leader在WaitForParallelWorkersToFinish中會推進自己的副本。  */ XLogRecPtr  ProcLastRecPtr = InvalidXLogRecPtr; XLogRecPtr  XactLastRecEnd = InvalidXLogRecPtr; XLogRecPtr XactLastCommitEnd = InvalidXLogRecPtr;  /* For WALInsertLockAcquire/Release functions */ //用于WALInsertLockAcquire/Release函數(shù) static int  MyLockNo = 0; static bool holdingAllLocks = false;  /*  * Private, possibly out-of-date copy of shared LogwrtResult.  * See discussion above.  * 進程私有的可能已過期的共享LogwrtResult變量的拷貝.  */ static XLogwrtResult LogwrtResult = {0, 0};  /* The number of bytes in a WAL segment usable for WAL data. */ //WAL segment file中可用于WAL data的字節(jié)數(shù)(不包括page header) static int UsableBytesInSegment;

宏定義
XLogRegisterBuffer函數(shù)使用的flags

/* flags for XLogRegisterBuffer */ //XLogRegisterBuffer函數(shù)使用的flags #define REGBUF_FORCE_IMAGE  0x01    /* 強制執(zhí)行full-page-write;force a full-page image */ #define REGBUF_NO_IMAGE     0x02    /* 不需要FPI;don't take a full-page image */ #define REGBUF_WILL_INIT    (0x04 | 0x02)   /* 在回放時重新初始化page(表示NO_IMAGE);                                              * page will be re-initialized at                                              * replay (implies NO_IMAGE) */ #define REGBUF_STANDARD     0x08    /* 標準的page layout(數(shù)據(jù)在pd_lower和pd_upper之間的數(shù)據(jù)會被跳過)                                      * page follows "standard" page layout,                                      * (data between pd_lower and pd_upper                                      * will be skipped) */ #define REGBUF_KEEP_DATA    0x10    /* include data even if a full-page image                                       * is taken */ /*  * Flag bits for the record being inserted, set using XLogSetRecordFlags().  */ #define XLOG_INCLUDE_ORIGIN     0x01    /* include the replication origin */ #define XLOG_MARK_UNIMPORTANT   0x02    /* record not important for durability */       #define XLogSegmentOffset(xlogptr, wal_segsz_bytes) \     ((xlogptr) & ((wal_segsz_bytes) - 1)) /*  * Calculate the amount of space left on the page after 'endptr'. Beware  * multiple evaluation!  * 計算page中在"endptr"后的剩余空閑空間.注意multiple evaluation!   */ #define INSERT_FREESPACE(endptr)    \     (((endptr) % XLOG_BLCKSZ == 0) ? 0 : (XLOG_BLCKSZ - (endptr) % XLOG_BLCKSZ))

XLogRecData
xloginsert.c中的函數(shù)構(gòu)造一個XLogRecData結(jié)構(gòu)體鏈用于標識最后的WAL記錄

/*  * The functions in xloginsert.c construct a chain of XLogRecData structs  * to represent the final WAL record.  * xloginsert.c中的函數(shù)構(gòu)造一個XLogRecData結(jié)構(gòu)體鏈用于標識最后的WAL記錄  */ typedef struct XLogRecData {     //鏈中的下一個結(jié)構(gòu)體,如無則為NULL     struct XLogRecData *next;   /* next struct in chain, or NULL */     //rmgr數(shù)據(jù)的起始地址     char       *data;           /* start of rmgr data to include */     //rmgr數(shù)據(jù)大小     uint32      len;            /* length of rmgr data to include */ } XLogRecData;

二、源碼解讀

ReserveXLogInsertLocation
在WAL(buffer)中為給定大小的記錄預留合適的空間。*StartPos設置為預留部分的開頭,*EndPos設置為其結(jié)尾+1。*PrePtr設置為前一記錄的開頭;它用于設置該記錄的xl_prev變量。

/*  * Reserves the right amount of space for a record of given size from the WAL.  * *StartPos is set to the beginning of the reserved section, *EndPos to  * its end+1. *PrevPtr is set to the beginning of the previous record; it is  * used to set the xl_prev of this record.  * 在WAL(buffer)中為給定大小的記錄預留合適的空間。  * *StartPos設置為預留部分的開頭,*EndPos設置為其結(jié)尾+1。  * *PrePtr設置為前一記錄的開頭;它用于設置該記錄的xl_prev。  *  * This is the performance critical part of XLogInsert that must be serialized  * across backends. The rest can happen mostly in parallel. Try to keep this  * section as short as possible, insertpos_lck can be heavily contended on a  * busy system.  * 這是XLogInsert中與性能密切相關(guān)的部分,必須在后臺進程之間序列執(zhí)行。  * 其余的大部分可以同時發(fā)生。  * 盡量精簡這部分的邏輯,insertpos_lck可以在繁忙的系統(tǒng)上存在激烈的競爭。  *  * NB: The space calculation here must match the code in CopyXLogRecordToWAL,  * where we actually copy the record to the reserved space.  * 注意:這里計算的空間必須與CopyXLogRecordToWAL()函數(shù)一致,  *   在CopyXLogRecordToWAL中會實際拷貝數(shù)據(jù)到預留空間中.  */ static void ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos, XLogRecPtr *EndPos,                           XLogRecPtr *PrevPtr) {     XLogCtlInsert *Insert = &XLogCtl->Insert;//插入控制器     uint64      startbytepos;//開始位置     uint64      endbytepos;//結(jié)束位置     uint64      prevbytepos;//上一位置      size = MAXALIGN(size);//大小對齊      /* All (non xlog-switch) records should contain data. */     //除了xlog-switch外,所有的記錄都應該包含數(shù)據(jù).     Assert(size > SizeOfXLogRecord);      /*      * The duration the spinlock needs to be held is minimized by minimizing      * the calculations that have to be done while holding the lock. The      * current tip of reserved WAL is kept in CurrBytePos, as a byte position      * that only counts "usable" bytes in WAL, that is, it excludes all WAL      * page headers. The mapping between "usable" byte positions and physical      * positions (XLogRecPtrs) can be done outside the locked region, and      * because the usable byte position doesn't include any headers, reserving      * X bytes from WAL is almost as simple as "CurrBytePos += X".      * spinlock需要持有的時間通過最小化必須持有鎖的計算邏輯達到最小化。      * 預留的WAL空間通過CurrBytePos變量(大小一個字節(jié))保存,      *   它只計算WAL中的“可用”字節(jié),也就是說,它排除了所有的WAL page header。      * “可用”字節(jié)位置和物理位置(XLogRecPtrs)之間的映射可以在鎖定區(qū)域之外完成,      *   而且由于可用字節(jié)位置不包含任何header,從WAL預留X字節(jié)的大小幾乎和“CurrBytePos += X”一樣簡單。      */     SpinLockAcquire(&Insert->insertpos_lck);//申請鎖     //開始位置     startbytepos = Insert->CurrBytePos;     //結(jié)束位置     endbytepos = startbytepos + size;     //上一位置     prevbytepos = Insert->PrevBytePos;     //調(diào)整控制器的相關(guān)變量     Insert->CurrBytePos = endbytepos;     Insert->PrevBytePos = startbytepos;     //釋放鎖     SpinLockRelease(&Insert->insertpos_lck);     //返回值     //計算開始/結(jié)束/上一位置偏移     *StartPos = XLogBytePosToRecPtr(startbytepos);     *EndPos = XLogBytePosToEndRecPtr(endbytepos);     *PrevPtr = XLogBytePosToRecPtr(prevbytepos);      /*      * Check that the conversions between "usable byte positions" and      * XLogRecPtrs work consistently in both directions.      * 檢查雙向轉(zhuǎn)換之后的值是一致的.      */     Assert(XLogRecPtrToBytePos(*StartPos) == startbytepos);     Assert(XLogRecPtrToBytePos(*EndPos) == endbytepos);     Assert(XLogRecPtrToBytePos(*PrevPtr) == prevbytepos); }   /*  * Converts a "usable byte position" to XLogRecPtr. A usable byte position  * is the position starting from the beginning of WAL, excluding all WAL  * page headers.  * 將“可用字節(jié)位置”轉(zhuǎn)換為XLogRecPtr。  * 可用字節(jié)位置是從WAL開始的位置,不包括所有WAL page header。  */ static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos) {     uint64      fullsegs;     uint64      fullpages;     uint64      bytesleft;     uint32      seg_offset;     XLogRecPtr  result;      fullsegs = bytepos / UsableBytesInSegment;     bytesleft = bytepos % UsableBytesInSegment;      if (bytesleft < XLOG_BLCKSZ - SizeOfXLogLongPHD)     {         //剩余的字節(jié)數(shù) < XLOG_BLCKSZ - SizeOfXLogLongPHD             /* fits on first page of segment */         //填充在segment的第一個page中         seg_offset = bytesleft + SizeOfXLogLongPHD;     }     else     {         //剩余的字節(jié)數(shù) >= XLOG_BLCKSZ - SizeOfXLogLongPHD             /* account for the first page on segment with long header */         //在segment中說明long header         seg_offset = XLOG_BLCKSZ;         bytesleft -= XLOG_BLCKSZ - SizeOfXLogLongPHD;          fullpages = bytesleft / UsableBytesInPage;         bytesleft = bytesleft % UsableBytesInPage;          seg_offset += fullpages * XLOG_BLCKSZ + bytesleft + SizeOfXLogShortPHD;     }      XLogSegNoOffsetToRecPtr(fullsegs, seg_offset, wal_segment_size, result);      return result; }  /* The number of bytes in a WAL segment usable for WAL data. */ //WAL segment file中可用于WAL data的字節(jié)數(shù)(不包括page header) static int UsableBytesInSegment;

CopyXLogRecordToWAL
CopyXLogRecordToWAL是XLogInsertRecord中的子過程,用于拷貝XLOG Record到WAL中的保留區(qū)域.

/*  * Subroutine of XLogInsertRecord.  Copies a WAL record to an already-reserved  * area in the WAL.  * XLogInsertRecord中的子過程.  * 拷貝XLOG Record到WAL中的保留區(qū)域.  */ static void CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,                     XLogRecPtr StartPos, XLogRecPtr EndPos) {     char       *currpos;//當前指針位置     int         freespace;//空閑空間     int         written;//已寫入的大小     XLogRecPtr  CurrPos;//事務日志位置     XLogPageHeader pagehdr;//Page Header      /*      * Get a pointer to the right place in the right WAL buffer to start      * inserting to.      * 在合適的WAL buffer中獲取指針用于確定插入的位置      */     CurrPos = StartPos;//賦值為開始位置     currpos = GetXLogBuffer(CurrPos);//獲取buffer指針     freespace = INSERT_FREESPACE(CurrPos);//獲取空閑空間大小      /*      * there should be enough space for at least the first field (xl_tot_len)      * on this page.      * 在該頁上最起碼有第一個字段(xl_tot_len)的存儲空間      */     Assert(freespace >= sizeof(uint32));      /* Copy record data */     //拷貝記錄數(shù)據(jù)     written = 0;     while (rdata != NULL)//循環(huán)     {         char       *rdata_data = rdata->data;//指針         int         rdata_len = rdata->len;//大小          while (rdata_len > freespace)//循環(huán)         {             /*              * Write what fits on this page, and continue on the next page.              * 該頁能寫多少就寫多少,寫不完就繼續(xù)下一頁.              */             //確保最起碼剩余SizeOfXLogShortPHD的頭部數(shù)據(jù)存儲空間             Assert(CurrPos % XLOG_BLCKSZ >= SizeOfXLogShortPHD || freespace == 0);             //內(nèi)存拷貝             memcpy(currpos, rdata_data, freespace);             //指針調(diào)整             rdata_data += freespace;             //大小調(diào)整             rdata_len -= freespace;             //寫入大小調(diào)整             written += freespace;             //當前位置調(diào)整             CurrPos += freespace;              /*              * Get pointer to beginning of next page, and set the xlp_rem_len              * in the page header. Set XLP_FIRST_IS_CONTRECORD.              * 獲取下一頁的開始指針,并在下一頁的header中設置xlp_rem_len.              * 同時設置XLP_FIRST_IS_CONTRECORD標記.              *              * It's safe to set the contrecord flag and xlp_rem_len without a              * lock on the page. All the other flags were already set when the              * page was initialized, in AdvanceXLInsertBuffer, and we're the              * only backend that needs to set the contrecord flag.              * 就算不持有頁鎖,設置contrecord標記和xlp_rem_len也是安全的.              * 在頁面初始化的時候,所有其他標記已通過AdvanceXLInsertBuffer函數(shù)初始化,              *   我們是需要設置contrecord標記的唯一一個后臺進程,不會有其他進程了.              */             currpos = GetXLogBuffer(CurrPos);//獲取buffer             pagehdr = (XLogPageHeader) currpos;//獲取page header             pagehdr->xlp_rem_len = write_len - written;//設置xlp_rem_len             pagehdr->xlp_info |= XLP_FIRST_IS_CONTRECORD;//設置標記              /* skip over the page header */             //跳過page header             if (XLogSegmentOffset(CurrPos, wal_segment_size) == 0)//第一個page             {                 CurrPos += SizeOfXLogLongPHD;//Long Header                 currpos += SizeOfXLogLongPHD;             }             else             {                 CurrPos += SizeOfXLogShortPHD;//不是第一個page,Short Header                 currpos += SizeOfXLogShortPHD;             }             freespace = INSERT_FREESPACE(CurrPos);//獲取空閑空間         }         //再次驗證         Assert(CurrPos % XLOG_BLCKSZ >= SizeOfXLogShortPHD || rdata_len == 0);         //內(nèi)存拷貝(這時候rdata_len <= freespace)         memcpy(currpos, rdata_data, rdata_len);         currpos += rdata_len;//調(diào)整指針         CurrPos += rdata_len;//調(diào)整指針         freespace -= rdata_len;//減少空閑空間         written += rdata_len;//調(diào)整已寫入大小          rdata = rdata->next;//下一批數(shù)據(jù)     }     Assert(written == write_len);//確保已寫入 == 需寫入大小      /*      * If this was an xlog-switch, it's not enough to write the switch record,      * we also have to consume all the remaining space in the WAL segment.  We      * have already reserved that space, but we need to actually fill it.      * 如果是xlog-switch并且沒有足夠的空間寫切換的記錄,      *   這時候不得不消費WAL segment剩余的空間.      * 我們已經(jīng)預留了空間,但需要執(zhí)行實際的填充.      */     if (isLogSwitch && XLogSegmentOffset(CurrPos, wal_segment_size) != 0)     {         /* An xlog-switch record doesn't contain any data besides the header */         //在header后,xlog-switch沒有包含任何數(shù)據(jù).         Assert(write_len == SizeOfXLogRecord);          /* Assert that we did reserve the right amount of space */         //驗證預留了合適的空間         Assert(XLogSegmentOffset(EndPos, wal_segment_size) == 0);          /* Use up all the remaining space on the current page */         //在當前頁面使用所有的剩余空間         CurrPos += freespace;          /*          * Cause all remaining pages in the segment to be flushed, leaving the          * XLog position where it should be, at the start of the next segment.          * We do this one page at a time, to make sure we don't deadlock          * against ourselves if wal_buffers < wal_segment_size.          * 由于該segment中所有剩余pages將被刷出,把XLog位置指向下一個segment的開始.          * 一個page我們只做一次,在wal_buffers < wal_segment_size的情況下,          *   確保我們自己不會出現(xiàn)死鎖.          */         while (CurrPos < EndPos)//循環(huán)         {             /*              * The minimal action to flush the page would be to call              * WALInsertLockUpdateInsertingAt(CurrPos) followed by              * AdvanceXLInsertBuffer(...).  The page would be left initialized              * mostly to zeros, except for the page header (always the short              * variant, as this is never a segment's first page).              * 刷出page的最小化動作是:調(diào)用WALInsertLockUpdateInsertingAt(CurrPos)              *   然后接著調(diào)用AdvanceXLInsertBuffer(...).              * 除了page header(通常為short格式,除了segment的第一個page)外,其余部分均初始化為ascii 0.              *               * The large vistas of zeros are good for compressibility, but the              * headers interrupting them every XLOG_BLCKSZ (with values that              * differ from page to page) are not.  The effect varies with              * compression tool, but bzip2 for instance compresses about an              * order of magnitude worse if those headers are left in place.              * 連續(xù)的ascii 0非常適合壓縮,但每個page的頭部數(shù)據(jù)(用于分隔page&page)把這些0隔開了.              * 這種效果隨壓縮工具的不同而不同,但是如果保留這些頭文件,則bzip2的壓縮效果會差一個數(shù)量級。              *              * Rather than complicating AdvanceXLInsertBuffer itself (which is              * called in heavily-loaded circumstances as well as this lightly-              * loaded one) with variant behavior, we just use GetXLogBuffer              * (which itself calls the two methods we need) to get the pointer              * and zero most of the page.  Then we just zero the page header.              * 與其讓AdvanceXLInsertBuffer本身(在重載環(huán)境和這個負載較輕的環(huán)境中調(diào)用)變得復雜,              *  不如使用GetXLogBuffer(調(diào)用了我們需要的兩個方法)來初始化page(初始化為ascii 0)/              * 然后把page header設置為ascii 0.              */             currpos = GetXLogBuffer(CurrPos);//獲取buffer             MemSet(currpos, 0, SizeOfXLogShortPHD);//設置頭部為ascii 0              CurrPos += XLOG_BLCKSZ;//修改指針         }     }     else     {         /* Align the end position, so that the next record starts aligned */         //對齊末尾位置,以便下一個記錄可以從對齊的位置開始         CurrPos = MAXALIGN64(CurrPos);     }      if (CurrPos != EndPos)//驗證         elog(PANIC, "space reserved for WAL record does not match what was written"); }

三、跟蹤分析

測試腳本如下:

drop table t_wal_longtext; create table t_wal_longtext(c1 int not null,c2  varchar(3000),c3 varchar(3000),c4 varchar(3000)); insert into t_wal_longtext(c1,c2,c3,c4)  select i,rpad('C2-'||i,3000,'2'),rpad('C3-'||i,3000,'3'),rpad('C4-'||i,3000,'4')  from generate_series(1,7) as i;

ReserveXLogInsertLocation
插入數(shù)據(jù):

insert into t_wal_longtext(c1,c2,c3,c4) VALUES(8,'C2-8','C3-8','C4-8');

設置斷點,進入ReserveXLogInsertLocation

(gdb) b ReserveXLogInsertLocation Breakpoint 1 at 0x54d574: file xlog.c, line 1244. (gdb) c Continuing.  Breakpoint 1, ReserveXLogInsertLocation (size=74, StartPos=0x7ffebea9d768, EndPos=0x7ffebea9d760, PrevPtr=0x244f4c8)     at xlog.c:1244 1244        XLogCtlInsert *Insert = &XLogCtl->Insert; (gdb)

輸入?yún)?shù):
size=74, 這是待插入XLOG Record的大小,其他三個為待設置的值.
繼續(xù)執(zhí)行.
對齊,74->80(要求為8的N倍,unit64占用8bytes,因此要求8的倍數(shù))

(gdb) n 1249        size = MAXALIGN(size); (gdb)  1252        Assert(size > SizeOfXLogRecord); (gdb) p size $1 = 80 (gdb)

查看插入控制器的信息,其中:
CurrBytePos = 5498377520,十六進制為0x147BA9530
PrevBytePos = 5498377464,十六進制為0x147BA94F8
RedoRecPtr = 5514382312,十六進制為0x148AECBE8 --> 對應pg_control中的Latest checkpoint's REDO location

(gdb) n 1264        SpinLockAcquire(&Insert->insertpos_lck); (gdb)  1266        startbytepos = Insert->CurrBytePos; (gdb) p *Insert $2 = {insertpos_lck = 1 '\001', CurrBytePos = 5498377520, PrevBytePos = 5498377464, pad = '\000' <repeats 127 times>,    RedoRecPtr = 5514382312, forcePageWrites = false, fullPageWrites = true, exclusiveBackupState = EXCLUSIVE_BACKUP_NONE,    nonExclusiveBackups = 0, lastBackupStart = 0, WALInsertLocks = 0x7f97d1eeb100} (gdb)

設置相應的值.
值得注意的是插入控制器Insert中的位置信息是不包括page header等信息,是純粹可用的日志數(shù)據(jù),因此數(shù)值要比WAL segment file的數(shù)值小.

(gdb) n 1267        endbytepos = startbytepos + size; (gdb)  1268        prevbytepos = Insert->PrevBytePos; (gdb)  1269        Insert->CurrBytePos = endbytepos; (gdb)  1270        Insert->PrevBytePos = startbytepos; (gdb)  1272        SpinLockRelease(&Insert->insertpos_lck); (gdb)

如前所述,需要將“可用字節(jié)位置”轉(zhuǎn)換為XLogRecPtr。
計算實際的開始/結(jié)束/上一位置.
StartPos = 5514538672,0x148B12EB0
EndPos = 5514538752,0x148B12F00
PrevPtr = 5514538616,0x148B12E78

(gdb) n 1274        *StartPos = XLogBytePosToRecPtr(startbytepos); (gdb)  1275        *EndPos = XLogBytePosToEndRecPtr(endbytepos); (gdb)  1276        *PrevPtr = XLogBytePosToRecPtr(prevbytepos); (gdb)  1282        Assert(XLogRecPtrToBytePos(*StartPos) == startbytepos); (gdb) p *StartPos $4 = 5514538672 (gdb) p *EndPos $5 = 5514538752 (gdb) p *PrevPtr $6 = 5514538616 (gdb)

驗證相互轉(zhuǎn)換是沒有問題的.

(gdb) n 1283        Assert(XLogRecPtrToBytePos(*EndPos) == endbytepos); (gdb)  1284        Assert(XLogRecPtrToBytePos(*PrevPtr) == prevbytepos); (gdb)  1285    } (gdb)  XLogInsertRecord (rdata=0xf9cc70 <hdr_rdt>, fpw_lsn=5514538520, flags=1 '\001') at xlog.c:1072 1072            inserted = true; (gdb)

DONE!

CopyXLogRecordToWAL-場景1:不跨WAL page
測試腳本如下:

insert into t_wal_longtext(c1,c2,c3,c4) VALUES(8,'C2-8','C3-8','C4-8');

繼續(xù)上一條SQL的跟蹤.
設置斷點,進入CopyXLogRecordToWAL

(gdb) b CopyXLogRecordToWAL Breakpoint 3 at 0x54dcdf: file xlog.c, line 1479. (gdb) c Continuing.  Breakpoint 3, CopyXLogRecordToWAL (write_len=74, isLogSwitch=false, rdata=0xf9cc70 <hdr_rdt>, StartPos=5514538672,      EndPos=5514538752) at xlog.c:1479 1479        CurrPos = StartPos; (gdb)

輸入?yún)?shù):
write_len=74, --> 待寫入大小
isLogSwitch=false, --> 是否日志切換(不需要)
rdata=0xf9cc70 <\hdr_rdt>, --> 需寫入的數(shù)據(jù)地址
StartPos=5514538672, --> 開始位置
EndPos=5514538752 --> 結(jié)束位置

(gdb) n 1480        currpos = GetXLogBuffer(CurrPos); (gdb)

在合適的WAL buffer中獲取指針用于確定插入的位置.
進入函數(shù)GetXLogBuffer,輸入?yún)?shù)ptr為5514538672,即開始位置.

(gdb) step GetXLogBuffer (ptr=5514538672) at xlog.c:1854 1854        if (ptr / XLOG_BLCKSZ == cachedPage) (gdb) p ptr / 8192 --> 取模 $7 = 673161 (gdb)  (gdb) p cachedPage $8 = 673161 (gdb)

GetXLogBuffer->ptr / XLOG_BLCKSZ == cachedPage,進入相應的處理邏輯
注意:cachedPage是靜態(tài)變量,具體在哪個地方賦值,后續(xù)需再行分析

(gdb) n 1856            Assert(((XLogPageHeader) cachedPos)->xlp_magic == XLOG_PAGE_MAGIC); (gdb)  1857            Assert(((XLogPageHeader) cachedPos)->xlp_pageaddr == ptr - (ptr % XLOG_BLCKSZ)); (gdb)  1858            return cachedPos + ptr % XLOG_BLCKSZ;

GetXLogBuffer->cachedPos開頭是XLogPageHeader結(jié)構(gòu)體

(gdb) p *((XLogPageHeader) cachedPos) $14 = {xlp_magic = 53400, xlp_info = 5, xlp_tli = 1, xlp_pageaddr = 5514534912, xlp_rem_len = 71} (gdb)  (gdb) x/24bx (0x7f97d29fe000) 0x7f97d29fe000: 0x98    0xd0    0x05    0x00    0x01    0x00    0x00    0x00 0x7f97d29fe008: 0x00    0x20    0xb1    0x48    0x01    0x00    0x00    0x00 0x7f97d29fe010: 0x47    0x00    0x00    0x00    0x00    0x00    0x00    0x00

回到CopyXLogRecordToWAL,buffer的地址為0x7f97d29feeb0

(gdb) n 1945    } (gdb)  CopyXLogRecordToWAL (write_len=74, isLogSwitch=false, rdata=0xf9cc70 <hdr_rdt>, StartPos=5514538672, EndPos=5514538752)     at xlog.c:1481 1481        freespace = INSERT_FREESPACE(CurrPos); (gdb)  (gdb) p currpos $16 = 0x7f97d29feeb0 "" (gdb)

計算空閑空間,確保在該頁上最起碼有第一個字段(xl_tot_len)的存儲空間(4字節(jié)).

(gdb) n 1487        Assert(freespace >= sizeof(uint32)); (gdb) p freespace $21 = 4432 (gdb)

開始拷貝記錄數(shù)據(jù).

(gdb) n 1490        written = 0; --> 記錄已寫入的大小 (gdb)  1491        while (rdata != NULL)

rdata的分析詳見第四部分,繼續(xù)執(zhí)行

(gdb) n 1493            char       *rdata_data = rdata->data; (gdb)  1494            int         rdata_len = rdata->len; (gdb)  1496            while (rdata_len > freespace) (gdb) p rdata_len $34 = 46 (gdb) p freespace $35 = 4432 (gdb)

rdata_len < freespace,無需進入子循環(huán).
再次進行驗證沒有問題,執(zhí)行內(nèi)存拷貝.

(gdb) n 1536            Assert(CurrPos % XLOG_BLCKSZ >= SizeOfXLogShortPHD || rdata_len == 0); (gdb)  1537            memcpy(currpos, rdata_data, rdata_len); (gdb)  1538            currpos += rdata_len; (gdb)  1539            CurrPos += rdata_len; (gdb)  1540            freespace -= rdata_len; (gdb)  1541            written += rdata_len; (gdb)  1543            rdata = rdata->next; (gdb)  1491        while (rdata != NULL) (gdb) p currpos $36 = 0x7f97d29feede "" (gdb) p CurrPos $37 = 5514538718 (gdb) p freespace $38 = 4386 (gdb) p written $39 = 46 (gdb)

rdata共有四部分,繼續(xù)寫入第二/三/四部分.

... 1491        while (rdata != NULL) (gdb)  1493            char       *rdata_data = rdata->data; (gdb)  1494            int         rdata_len = rdata->len; (gdb)  1496            while (rdata_len > freespace) (gdb)  1536            Assert(CurrPos % XLOG_BLCKSZ >= SizeOfXLogShortPHD || rdata_len == 0); (gdb)  1537            memcpy(currpos, rdata_data, rdata_len); (gdb)  1538            currpos += rdata_len; (gdb)  1539            CurrPos += rdata_len; (gdb)  1540            freespace -= rdata_len; (gdb)  1541            written += rdata_len; (gdb)  1543            rdata = rdata->next; (gdb)  1491        while (rdata != NULL) (gdb)

完成寫入74bytes

(gdb)  1545        Assert(written == write_len); (gdb) p written $40 = 74 (gdb)

無需執(zhí)行日志切換的相關(guān)操作.
對齊CurrPos

(gdb) n 1552        if (isLogSwitch && XLogSegmentOffset(CurrPos, wal_segment_size) != 0) (gdb)  1599            CurrPos = MAXALIGN64(CurrPos); (gdb) p CurrPos $41 = 5514538746 (gdb) n 1602        if (CurrPos != EndPos) (gdb) p CurrPos $42 = 5514538752 (gdb)  (gdb) p 5514538746 % 8 $44 = 2 --> 需補6個字節(jié),5514538746 --> 5514538752

對齊后,CurrPos == EndPos,否則報錯!

(gdb) p EndPos $45 = 5514538752

結(jié)束調(diào)用

(gdb) n 1604    } (gdb)  XLogInsertRecord (rdata=0xf9cc70 <hdr_rdt>, fpw_lsn=5514538520, flags=1 '\001') at xlog.c:1098 1098            if ((flags & XLOG_MARK_UNIMPORTANT) == 0) (gdb)

DONE!

CopyXLogRecordToWAL-場景2:跨WAL page 后續(xù)再行分析

四、再論WAL Record

在內(nèi)存中,WAL Record通過rdata存儲,該變量其實是全局靜態(tài)變量hdr_rdt,類型為XLogRecData,XLOG Record通過XLogRecData鏈表組織起來(這個設計很贊,寫入無需理會結(jié)構(gòu),按鏈表逐個寫數(shù)據(jù)即可).
rdata由4部分組成:
第一部分是XLogRecord + XLogRecordBlockHeader + XLogRecordDataHeaderShort,共46字節(jié)
第二部分是xl_heap_header,5個字節(jié)
第三部分是tuple data,20個字節(jié)
第四部分是xl_heap_insert,3個字節(jié)

------------------------------------------------------------------- 1 (gdb) p *rdata  $22 = {next = 0x244f2c0, data = 0x244f4c0 "J", len = 46}  (gdb) p *(XLogRecord *)rdata->data --> XLogRecord $27 = {xl_tot_len = 74, xl_xid = 2268, xl_prev = 5514538616, xl_info = 0 '\000', xl_rmid = 10 '\n', xl_crc = 1158677949} (gdb) p *(XLogRecordBlockHeader *)(0x244f4c0+24) --> XLogRecordBlockHeader $29 = {id = 0 '\000', fork_flags = 32 ' ', data_length = 25} (gdb) x/2bx (0x244f4c0+44) --> XLogRecordDataHeaderShort 0x244f4ec:  0xff    0x03 ------------------------------------------------------------------- 2  (gdb) p *rdata->next $23 = {next = 0x244f2d8, data = 0x7ffebea9d830 "\004", len = 5} (gdb) p *(xl_heap_header *)rdata->next->data $32 = {t_infomask2 = 4, t_infomask = 2050, t_hoff = 24 '\030'} ------------------------------------------------------------------- 3 (gdb) p *rdata->next->next $24 = {next = 0x244f2a8, data = 0x24e6a2f "", len = 20} (gdb) x/20bc  0x24e6a2f 0x24e6a2f:  0 '\000'    8 '\b'  0 '\000'    0 '\000'    0 '\000'    11 '\v' 67 'C'  50 '2' 0x24e6a37:  45 '-'  56 '8'  11 '\v' 67 'C'  51 '3'  45 '-'  56 '8'  11 '\v' 0x24e6a3f:  67 'C'  52 '4'  45 '-'  56 '8' (gdb)  ------------------------------------------------------------------- 4 (gdb) p *rdata->next->next->next $25 = {next = 0x0, data = 0x7ffebea9d840 "\b", len = 3} (gdb)  (gdb) p *(xl_heap_insert *)rdata->next->next->next->data $33 = {offnum = 8, flags = 0 '\000'}

“PostgreSQL中ReserveXLogInsertLocation和CopyXLogRecordToWAL函數(shù)的實現(xiàn)邏輯是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI