PostgreSQL 原始碼解讀(212)- 後臺程式#11(checkpointer-SyncOneBuffer)

husthxd 發表於2019-07-25

本節介紹了checkpoint中用於刷一個髒page的函式:SyncOneBuffer,該函式在syncing期間處理一個buffer.

一、資料結構

巨集定義
checkpoints request flag bits,檢查點請求標記位定義.


/*
 * OR-able request flag bits for checkpoints.  The "cause" bits are used only
 * for logging purposes.  Note: the flags must be defined so that it's
 * sensible to OR together request flags arising from different requestors.
 */
/* These directly affect the behavior of CreateCheckPoint and subsidiaries */
#define CHECKPOINT_IS_SHUTDOWN  0x0001  /* Checkpoint is for shutdown */
#define CHECKPOINT_END_OF_RECOVERY  0x0002  /* Like shutdown checkpoint, but
                       * issued at end of WAL recovery */
#define CHECKPOINT_IMMEDIATE  0x0004  /* Do it without delays */
#define CHECKPOINT_FORCE    0x0008  /* Force even if no activity */
#define CHECKPOINT_FLUSH_ALL  0x0010  /* Flush all pages, including those
                     * belonging to unlogged tables */
/* These are important to RequestCheckpoint */
#define CHECKPOINT_WAIT     0x0020  /* Wait for completion */
#define CHECKPOINT_REQUESTED  0x0040  /* Checkpoint request has been made */
/* These indicate the cause of a checkpoint request */
#define CHECKPOINT_CAUSE_XLOG 0x0080  /* XLOG consumption */
#define CHECKPOINT_CAUSE_TIME 0x0100  /* Elapsed time */

二、原始碼解讀

SyncOneBuffer,在syncing期間處理一個buffer,其主要處理邏輯如下:
1.獲取buffer描述符
2.鎖定buffer
3.根據buffer狀態和輸入引數執行相關判斷/處理
4.釘住髒頁,上共享鎖,呼叫FlushBuffer刷盤
5.解鎖/解釘和其他收尾工作


/*
 * SyncOneBuffer -- process a single buffer during syncing.
 * 在syncing期間處理一個buffer
 *
 * If skip_recently_used is true, we don't write currently-pinned buffers, nor
 * buffers marked recently used, as these are not replacement candidates.
 * 如skip_recently_used為T,既不寫currently-pinned buffers,
 *   也不寫標記為最近使用的buffers,因為這些緩衝區不是可替代的緩衝區.
 *
 * Returns a bitmask containing the following flag bits:
 *  BUF_WRITTEN: we wrote the buffer.
 *  BUF_REUSABLE: buffer is available for replacement, ie, it has
 *    pin count 0 and usage count 0.
 * 返回位掩碼:
 *   BUF_WRITTEN: 已寫入buffer
 *   BUF_REUSABLE: buffer可用於替代(pin count和usage count均為0)
 *
 * (BUF_WRITTEN could be set in error if FlushBuffers finds the buffer clean
 * after locking it, but we don't care all that much.)
 *
 * Note: caller must have done ResourceOwnerEnlargeBuffers.
 */
static int
SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
{
  BufferDesc *bufHdr = GetBufferDescriptor(buf_id);
  int     result = 0;
  uint32    buf_state;
  BufferTag tag;
  ReservePrivateRefCountEntry();
  /*
   * Check whether buffer needs writing.
   * 檢查buffer是否需要寫入.
   *
   * We can make this check without taking the buffer content lock so long
   * as we mark pages dirty in access methods *before* logging changes with
   * XLogInsert(): if someone marks the buffer dirty just after our check we
   * don't worry because our checkpoint.redo points before log record for
   * upcoming changes and so we are not required to write such dirty buffer.
   * 在使用XLogInsert() logging變化前通過訪問方法標記pages為髒時,
   *   不需要持有鎖太長的時間來執行該檢查:
   * 因為如果某個程式在檢查後標記buffer為髒,
   *   在這種情況下checkpoint.redo指向了變化出現前的log位置,因此無需擔心,而且不必寫這樣的髒塊.
   */
  buf_state = LockBufHdr(bufHdr);
  if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
    BUF_STATE_GET_USAGECOUNT(buf_state) == 0)
  {
    result |= BUF_REUSABLE;
  }
  else if (skip_recently_used)
  {
    /* Caller told us not to write recently-used buffers */
    //跳過最近使用的buffer
    UnlockBufHdr(bufHdr, buf_state);
    return result;
  }
  if (!(buf_state & BM_VALID) || !(buf_state & BM_DIRTY))
  {
    /* It's clean, so nothing to do */
    //buffer無效或者不是髒塊
    UnlockBufHdr(bufHdr, buf_state);
    return result;
  }
  /*
   * Pin it, share-lock it, write it.  (FlushBuffer will do nothing if the
   * buffer is clean by the time we've locked it.)
   * 釘住它,上共享鎖,並刷到盤上.
   */
  PinBuffer_Locked(bufHdr);
  LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
  //呼叫FlushBuffer
  //If the caller has an smgr reference for the buffer's relation, pass it as the second parameter.  
  //If not, pass NULL.
  FlushBuffer(bufHdr, NULL);
  LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
  tag = bufHdr->tag;
  UnpinBuffer(bufHdr, true);
  ScheduleBufferTagForWriteback(wb_context, &tag);
  return result | BUF_WRITTEN;
}

FlushBuffer
FlushBuffer函式物理上把共享快取刷盤,主要實現函式還是smgrwrite(storage manager write).


/*
 * FlushBuffer
 *    Physically write out a shared buffer.
 * 物理上把共享快取刷盤.
 *
 * NOTE: this actually just passes the buffer contents to the kernel; the
 * real write to disk won't happen until the kernel feels like it.  This
 * is okay from our point of view since we can redo the changes from WAL.
 * However, we will need to force the changes to disk via fsync before
 * we can checkpoint WAL.
 * 只是把buffer內容發給os核心,何時真正寫盤由os來確定.
 * 在checkpoint WAL前需要通過fsync強制落盤.
 *
 * The caller must hold a pin on the buffer and have share-locked the
 * buffer contents.  (Note: a share-lock does not prevent updates of
 * hint bits in the buffer, so the page could change while the write
 * is in progress, but we assume that that will not invalidate the data
 * written.)
 * 呼叫者必須釘住了快取並且持有共享鎖.
 * (注意:共享鎖不會buffer中的hint bits的更新,因此在寫入期間page可能會出現變化,
 *  但我假定那樣不會讓寫入的資料無效)
 *
 * If the caller has an smgr reference for the buffer's relation, pass it
 * as the second parameter.  If not, pass NULL.
 */
static void
FlushBuffer(BufferDesc *buf, SMgrRelation reln)
{
  XLogRecPtr  recptr;
  ErrorContextCallback errcallback;
  instr_time  io_start,
        io_time;
  Block   bufBlock;
  char     *bufToWrite;
  uint32    buf_state;
  /*
   * Acquire the buffer's io_in_progress lock.  If StartBufferIO returns
   * false, then someone else flushed the buffer before we could, so we need
   * not do anything.
   */
  if (!StartBufferIO(buf, false))
    return;
  /* Setup error traceback support for ereport() */
  errcallback.callback = shared_buffer_write_error_callback;
  errcallback.arg = (void *) buf;
  errcallback.previous = error_context_stack;
  error_context_stack = &errcallback;
  /* Find smgr relation for buffer */
  if (reln == NULL)
    reln = smgropen(buf->tag.rnode, InvalidBackendId);
  TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
                    buf->tag.blockNum,
                    reln->smgr_rnode.node.spcNode,
                    reln->smgr_rnode.node.dbNode,
                    reln->smgr_rnode.node.relNode);
  buf_state = LockBufHdr(buf);
  /*
   * Run PageGetLSN while holding header lock, since we don't have the
   * buffer locked exclusively in all cases.
   */
  recptr = BufferGetLSN(buf);
  /* To check if block content changes while flushing. - vadim 01/17/97 */
  buf_state &= ~BM_JUST_DIRTIED;
  UnlockBufHdr(buf, buf_state);
  /*
   * Force XLOG flush up to buffer's LSN.  This implements the basic WAL
   * rule that log updates must hit disk before any of the data-file changes
   * they describe do.
   *
   * However, this rule does not apply to unlogged relations, which will be
   * lost after a crash anyway.  Most unlogged relation pages do not bear
   * LSNs since we never emit WAL records for them, and therefore flushing
   * up through the buffer LSN would be useless, but harmless.  However,
   * GiST indexes use LSNs internally to track page-splits, and therefore
   * unlogged GiST pages bear "fake" LSNs generated by
   * GetFakeLSNForUnloggedRel.  It is unlikely but possible that the fake
   * LSN counter could advance past the WAL insertion point; and if it did
   * happen, attempting to flush WAL through that location would fail, with
   * disastrous system-wide consequences.  To make sure that can't happen,
   * skip the flush if the buffer isn't permanent.
   */
  if (buf_state & BM_PERMANENT)
    XLogFlush(recptr);
  /*
   * Now it's safe to write buffer to disk. Note that no one else should
   * have been able to write it while we were busy with log flushing because
   * we have the io_in_progress lock.
   */
  bufBlock = BufHdrGetBlock(buf);
  /*
   * Update page checksum if desired.  Since we have only shared lock on the
   * buffer, other processes might be updating hint bits in it, so we must
   * copy the page to private storage if we do checksumming.
   */
  bufToWrite = PageSetChecksumCopy((Page) bufBlock, buf->tag.blockNum);
  if (track_io_timing)
    INSTR_TIME_SET_CURRENT(io_start);
  /*
   * bufToWrite is either the shared buffer or a copy, as appropriate.
   */
  smgrwrite(reln,
        buf->tag.forkNum,
        buf->tag.blockNum,
        bufToWrite,
        false);
  if (track_io_timing)
  {
    INSTR_TIME_SET_CURRENT(io_time);
    INSTR_TIME_SUBTRACT(io_time, io_start);
    pgstat_count_buffer_write_time(INSTR_TIME_GET_MICROSEC(io_time));
    INSTR_TIME_ADD(pgBufferUsage.blk_write_time, io_time);
  }
  pgBufferUsage.shared_blks_written++;
  /*
   * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and
   * end the io_in_progress state.
   */
  TerminateBufferIO(buf, true, 0);
  TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(buf->tag.forkNum,
                     buf->tag.blockNum,
                     reln->smgr_rnode.node.spcNode,
                     reln->smgr_rnode.node.dbNode,
                     reln->smgr_rnode.node.relNode);
  /* Pop the error context stack */
  error_context_stack = errcallback.previous;
}

三、跟蹤分析

測試指令碼


testdb=# update t_wal_ckpt set c2 = 'C4#'||substr(c2,4,40);
UPDATE 1
testdb=# checkpoint;

跟蹤分析


(gdb) handle SIGINT print nostop pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal        Stop  Print Pass to program Description
SIGINT        No  Yes Yes   Interrupt
(gdb) b SyncOneBuffer
Breakpoint 1 at 0x8a7167: file bufmgr.c, line 2357.
(gdb) c
Continuing.
Program received signal SIGINT, Interrupt.
Breakpoint 1, SyncOneBuffer (buf_id=0, skip_recently_used=false, wb_context=0x7fff27f5ae00) at bufmgr.c:2357
2357    BufferDesc *bufHdr = GetBufferDescriptor(buf_id);
(gdb) n
2358    int     result = 0;
(gdb) p *bufHdr
$1 = {tag = {rnode = {spcNode = 1663, dbNode = 16384, relNode = 221290}, forkNum = MAIN_FORKNUM, blockNum = 0}, buf_id = 0, 
  state = {value = 3548905472}, wait_backend_pid = 0, freeNext = -2, content_lock = {tranche = 53, state = {
      value = 536870912}, waiters = {head = 2147483647, tail = 2147483647}}}
(gdb) n
2362    ReservePrivateRefCountEntry();
(gdb) 
2373    buf_state = LockBufHdr(bufHdr);
(gdb) 
2375    if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
(gdb) 
2376      BUF_STATE_GET_USAGECOUNT(buf_state) == 0)
(gdb) 
2375    if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
(gdb) 
2380    else if (skip_recently_used)
(gdb) 
2387    if (!(buf_state & BM_VALID) || !(buf_state & BM_DIRTY))
(gdb) 
2398    PinBuffer_Locked(bufHdr);
(gdb) p buf_state
$2 = 3553099776
(gdb) n
2399    LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
(gdb) 
2401    FlushBuffer(bufHdr, NULL);
(gdb) step
FlushBuffer (buf=0x7fedc4a68300, reln=0x0) at bufmgr.c:2687
2687    if (!StartBufferIO(buf, false))
(gdb) n
2691    errcallback.callback = shared_buffer_write_error_callback;
(gdb) 
2692    errcallback.arg = (void *) buf;
(gdb) 
2693    errcallback.previous = error_context_stack;
(gdb) 
2694    error_context_stack = &errcallback;
(gdb) 
2697    if (reln == NULL)
(gdb) 
2698      reln = smgropen(buf->tag.rnode, InvalidBackendId);
(gdb) 
2700    TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum,
(gdb) 
2706    buf_state = LockBufHdr(buf);
(gdb) 
2712    recptr = BufferGetLSN(buf);
(gdb) 
2715    buf_state &= ~BM_JUST_DIRTIED;
(gdb) p recptr
$3 = 16953421760
(gdb) n
2716    UnlockBufHdr(buf, buf_state);
(gdb) 
2735    if (buf_state & BM_PERMANENT)
(gdb) 
2736      XLogFlush(recptr);
(gdb) 
2743    bufBlock = BufHdrGetBlock(buf);
(gdb) 
2750    bufToWrite = PageSetChecksumCopy((Page) bufBlock, buf->tag.blockNum);
(gdb) p bufBlock
$4 = (Block) 0x7fedc4e68300
(gdb) n
2752    if (track_io_timing)
(gdb) 
2758    smgrwrite(reln,
(gdb) 
2764    if (track_io_timing)
(gdb) 
2772    pgBufferUsage.shared_blks_written++;
(gdb) 
2778    TerminateBufferIO(buf, true, 0);
(gdb) 
2780    TRACE_POSTGRESQL_BUFFER_FLUSH_DONE(buf->tag.forkNum,
(gdb) 
2787    error_context_stack = errcallback.previous;
(gdb) 
2788  }
(gdb) 
SyncOneBuffer (buf_id=0, skip_recently_used=false, wb_context=0x7fff27f5ae00) at bufmgr.c:2403
2403    LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
(gdb) 
2405    tag = bufHdr->tag;
(gdb) 
2407    UnpinBuffer(bufHdr, true);
(gdb) 
2409    ScheduleBufferTagForWriteback(wb_context, &tag);
(gdb) 
2411    return result | BUF_WRITTEN;
(gdb) 
2412  }
(gdb)

四、參考資料

PG Source Code
PgSQL · 特性分析 · 談談checkpoint的排程

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2651712/,如需轉載,請註明出處,否則將追究法律責任。