【PostgreSQL】检查点机制 CheckpointerMain()

1.触发机制

WAL 和 Check Point:

WAL 是预写日志是大部分数据库异常恢复的标准手段,在 commit 将数据修改记录到库、表、索引文件之前,先更新内存中 page 页,再将操作记录写入到 WAL 日志中,保证数据库崩溃恢复时内存中还未来得及持久化的 page 缺失的操作记录可以从 WAL 日志中找,另外 WAL 日志也同时作为数据备份、远程复制使用。但是 WAL 日志不可能无限大、且越大恢复时间越长,所以需要 WAL 搭配 Check Point 技术,定时将缓冲区中的 page 脏页持久化到数据库。通常发生在下面几种情况:(1)内存中 page 脏页太多需要进行持久化;(2)内存中 page 缓冲区不够了需要持久化一部分腾出空间来缓存新的脏页;(3)WAL 日志文件过大了在清除一部分 WAL 内容时如果这些数据时 page 脏页需要进行持久化防止恢复时永久丢失。page 持久化后这一部分 WAL 内容也可以清除了。

PostgreSQL 触发 checkpoint 主要是下面几个流程中:

(1)停库(smart者fast模式)

(2)数据库recovery完成

(3)开始备份(pg_start_backup,也包括备份工具例如 pg_basebackup)

(4)手动执行checkpoint命令

(5)生成WAL日志达到一定数量,由几个参数共同决定(参考下面)

(6)超时机制,默认值 checkpoint_timeout = 5min

(7)删除数据库也会触发

(8)wal日志总大小超过参数max_wal_size设置

在 xlog.h 中定义了一些产生 checkpoint 的流程:

#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 */

#define CHECKPOINT_CAUSE_XLOG  0x0080 /* XLOG consumption */

#define CHECKPOINT_CAUSE_TIME  0x0100 /* Elapsed time */

控制后台 CheckPoint 调度的几个参数:

checkpoint_segments    指定产生多少 xlog 记录后执行 ChcekPoint。

checkpoint_completion_target    新产生的 xlog 除以 checkpoint_segments  得到一个数,如果小于这个配置则执行 CheckPonit。这个思想来源于根据 shard buffer 中脏数据达到一定比例来执行 CheckPoint。

checkpoint_timeout    在系统 for(;;) 中判断两次 CheckPoint 是否超过这个时间,超过了则强制执行一次 CheckPoint 。默认五分钟保证间隔执行。

以上便是checkpoint的调度机制。我们要注意调整上述几个参数时,不要让checkpoint产生过于频繁,否则频繁的fsync操作会是系统不稳定。比如,checkpoint_segments一般设置为16个以上,checkpoint_completion_target设为0.9,checkpoint_timeout为300秒,这样一般checkpoint的间隔能达到1分钟以上。

2.WAL文件、控制文件、LSN、Checkpoint、时间线

WAL、LSN、CheckPoint、控制文件的关系:

WAL 文件顺序记录了提交的数据修改记录,每个操作都有一条 xlog 日志而 LSN 就是这一条条日志的编号,在某一个 LSN 执行 Checkpoint 后,这个 LSN 之前的 WAL 日志所对应的内存的脏数据就持久化了,WAL 在这个 LSN 之前的日志也就不需要了,在执行了 CheckPoint 之后,控制文件会记录这一次执行的 CheckPoint 具体在哪个 WAL 文件的哪一条 xlog 记录上。控制文件主要的作用是记录系统状态,而其中最新的检查点只是其中一项。而出现在这些文件中的TimeLine 时间线字段主要是用于基于时间点恢复,看下面的过程就能很好理解这几个文件的依赖作用以加深源码理解。

另外 WAL(pgData/pg_wal)、Controller(pgData/global/pg_control)两个文件的存储目录不一样。这两个文件都比较重要,控制文件损坏影响的是系统恢复、wal 文件损坏影响的是备份。

首先查看 WAL 日志文件的规则:

00000001 | 00000000  | 000000 | 01

  时间线    | wal逻辑ID |   段号

查看当前的在线 WAL 文件(PGDATD/pg_wal下面):

select * from pg_ls_waldir() order by modification asc;

查询结果:

可以看到当前仅一个 WAL 日志,一个 WAL 日志中有多条 xlog 记录,查询 xlog 记录需要使用 pg_waldump 工具:

pg_waldump  /home/pgData/pg_wal/000000010000000000000001

查询结果如下:

可以看到每行 xlog 都有个编号也就是 lsn,一些典型的 xlog 记录描述(INSERT、COMMIT、CHECKPOINT_ONLINE)比如记录了一些历史已经执行过的 CheckPoint:

记录如下:

rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 0/0188BD38, prev 0/0188BD00, desc: CHECKPOINT_ONLINE redo 0/188BD00; tli 1

而控制文件则记录了最新的 CheckPoint,查询控制文件需要使用 pg_controldata 指令:

查询结果如下,其中和 CheckPoint 关键的几行,  0/188BD00 这个编号,就是 WAL 文件中的一条 xlog 的 LSN。查询:

pg_controldata -D /home/pgData 

查询结果:

Latest checkpoint location:           0/188BD38

Latest checkpoint's REDO location:    0/188BD00

Latest checkpoint's REDO WAL file:    000000010000000000000001

其中 checkpoint's REDO location、checkpoint location 两个点的区别在于,前者是重做点用于在崩溃恢复时重做的起点,特性是重做点之前的 xlog 都落盘了不需要在恢复时应用。而后者是执行完 checkPonit 之后记录的 xlog。

这个和检查点创建的流程有关,创建检查点时先记录 redo point,然后将脏页落盘,然后更新控制文件,最后记录 check point。所以看到会有这两个记录。

3.入口函数

调用入口:

PostmasterMain()    =>

StartupDataBase()     =>

StartChildProcess()     =>

AuxiliaryProcessMain()     =>

checkpointer.c CheckpointerMain()    =>    检查点主进程入口函数,checkpoint 也在这里面 for(;;) 执行。

xlog.c GetInsertRecPtr()     =>    获取当前插入位置 XLogCtl->LogwrtRqst.Write

xlog.c CreateCheckPoint()    或者  xlog.c CreateRestartPoint()    =>

xlog.c CheckPointGuts()    =>    就是底层 SharedMemory 到 Disk 进行 Flush 的入口函数

            relmapper.c CheckPointRelationMap()     =>    将共享内存里系统表和对应物理文件映射的map文件刷到磁盘。

            slot.c CheckPointReplicationSlots()    =>     将 ShardMemory 中所有 Slots 进行 Flush

            snapbuild.c CheckPointSnapBuild()     =>    移除所有复制操作不在需要的可串行化快照

            rewriteheap.c CheckPointLogicalRewriteHeap()    =>     移除逻辑复制的restart LSN位点前不再需要的mapping、将剩余的mappings刷盘,在后续回放是只需要处理已写入的mapping

            origin.c CheckPointReplicationOrigin()     =>    对重放的 remote_lsn 执行每个复制源的进度检查点。 确保我们在检查点 (local_lsn) 中引用的所有事务实际上都在磁盘上。

            clog.c CheckPointCLOG()      =>    将CLOG脏数据刷至磁盘,调用SimpleLruWriteAll接口实现

            commit_ts.c CheckPointCommitTs()    =>    将脏 commit 页写入磁盘

            subtrans.c CheckPointSUBTRANS()     =>    脏 subtrans 页写入磁盘

            multixact.c     =>    将脏的 multixact页写入磁盘

            predicte.c CheckPointPredicate()

            bufmgr.c CheckPointBuffers()     =>    将共享缓冲池中的所有脏页刷盘

            sync.c ProcessSyncRequests()     =>    处理同步队列请求

            twophase.c CheckPointTwoPhase()     =>    处理两阶段提交事务工作

smgr.c smgrcloseall()     =>    完成 checkpoint 后 关闭 smgr 存储介质管理器引用

xlog.c UpdateCheckPointDistanceEstimate()     =>    估算两次 CheckPoint 之间的 xlog 日志量

xlog.c RemoveOldXlogFiles()    => 删除无用的xlog记录 LSN=0

4.几个重要的结构体

和检查点相关的有几个比较关键的结构体:

CheckpointerShmemStruct:CheckpointerShmemStruct 这个是 CheckPoint 进程和其他进程通信的内存数据结构。

CheckPoint:检查点结构体,在执行完 CheckPoint 之后这个结构体作为一条 xlog 记录到 WAL 文件中。这个结构体封装了一次检查点操作而被其他结构体例如 ControlFileData 使用。

XLogCtl:用于操作更新 xlog 

ControlFileData:控制文件 ControlFileData 用来保存服务状态,记录检查点等信息在系统恢复时起作用:

ControlFileData 代码如下:

{

   uint64    system_identifier; // 数据库实例编号,确保当前控制文件由一个 pg 实例管理

   uint32    pg_control_version; // 控制文件版本号,控制文件的修改会随时更新这个版本号

   uint32    catalog_version_no; /* see catversion.h */

   DBState       state; // 数据库当前状态,例如正常运行、正常退出、异常退出等等

   pg_time_t  time;     // 控制文件修改的时间

   XLogRecPtr checkPoint;       // 最近一次执行 CheckPoint 的记录

   CheckPoint checkPointCopy; // 最近一次执行 CheckPoint 的记录的备份

   XLogRecPtr unloggedLSN;

   XLogRecPtr minRecoveryPoint;   // 归档恢复时必须恢复到的最小 xlog 的 LSN

   TimeLineID minRecoveryPointTLI;    // 归档恢复时必须恢复到的最小时间线

   XLogRecPtr backupStartPoint;   //  在线备份时进行 CheckPoint 的开始的 xlog 的 LSN

   XLogRecPtr backupEndPoint;  // 在线备份时进行 CheckPoint 的结尾的 xlog 的 LSN

   bool      backupEndRequired;  // 用于判断是否基于正确的在线备份集恢复

   int          wal_level;  // 系统配置: wal 级别,控制 xlog 需要至少记录哪些东西(默认minimal只提供崩溃恢复级别)

   bool      wal_log_hints;

   int          MaxConnections;

   int          max_worker_processes;

   int          max_wal_senders;

   int          max_prepared_xacts;

   int          max_locks_per_xact;

   bool      track_commit_timestamp;

} ControlFileData;

5.几个重要的函数

核心源码在检查点检测入口函数的 for(;;) 中,会检测两次 checkpoint 的间隔(默认300毫秒)并执行检查点。

checkpointer.c CheckpointerMain() 

void CheckpointerMain(void)

{

   sigjmp_buf local_sigjmp_buf;

   MemoryContext checkpointer_context;

   CheckpointerShmem->checkpointer_pid = MyProcPid;

   pqsignal(SIGHUP, SignalHandlerForConfigReload);

   pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */

   pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */

   /* SIGQUIT handler was already set up by InitPostmasterChild */

   pqsignal(SIGALRM, SIG_IGN);

   pqsignal(SIGPIPE, SIG_IGN);

   pqsignal(SIGUSR1, procsignal_sigusr1_handler);

   pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);

   pqsignal(SIGCHLD, SIG_DFL);

   last_checkpoint_time = last_xlog_switch_time = (pg_time_t) time(NULL);

   before_shmem_exit(pgstat_before_server_shutdown, 0);

   checkpointer_context = AllocSetContextCreate(TopMemoryContext,"Checkpointer",ALLOCSET_DEFAULT_SIZES);

   MemoryContextSwitchTo(checkpointer_context);

   if (sigsetjmp(local_sigjmp_buf, 1) != 0)

   {

      /* Since not using PG_TRY, must reset error stack by hand */

      error_context_stack = NULL;

      /* Prevent interrupts while cleaning up */

      HOLD_INTERRUPTS();

      /* Report the error to the server log */

      EmitErrorReport();

      LWLockReleaseAll();

      ConditionVariableCancelSleep();

      pgstat_report_wait_end();

      AbortBufferIO();

      UnlockBuffers();

      ReleaseAuxProcessResources(false);

      AtEOXact_Buffers(false);

      AtEOXact_SMgr();

      AtEOXact_Files(false);

      AtEOXact_HashTables(false);

      /* Warn any waiting backends that the checkpoint failed. */

      if (ckpt_active)

      {

         SpinLockAcquire(&CheckpointerShmem->ckpt_lck);

         CheckpointerShmem->ckpt_failed++;

         CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started;

         SpinLockRelease(&CheckpointerShmem->ckpt_lck);

         ConditionVariableBroadcast(&CheckpointerShmem->done_cv);

         ckpt_active = false;

      }

      MemoryContextSwitchTo(checkpointer_context);

      FlushErrorState();

      /* Flush any leaked data in the top-level context */

      MemoryContextResetAndDeleteChildren(checkpointer_context);

      /* Now we can allow interrupts again */

      RESUME_INTERRUPTS();

      pg_usleep(1000000L);

      smgrcloseall();

   }

   /* We can now handle ereport(ERROR) */

   PG_exception_stack = &local_sigjmp_buf;

   PG_SETMASK(&UnBlockSig);

   UpdateSharedMemoryConfig();

   ProcGlobal->checkpointerLatch = &MyProc->procLatch;

    //无条件for,未触发 checkpoint 时一直在 waitLatch 和 sleep

   for (;;)

   {

        // 是否在本次 loop 执行 checkpoint

      bool      do_checkpoint = false;

      int          flags = 0;

      pg_time_t  now;

        // 自从上次 checkpoint 已经过去了多久

      int          elapsed_secs;

        // 剩余的超时时间

      int          cur_timeout;

      ResetLatch(MyLatch);

      AbsorbSyncRequests();

      HandleCheckpointerInterrupts();

      if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->ckpt_flags)

      {

         do_checkpoint = true;

         PendingCheckpointerStats.requested_checkpoints++;

      }

      // 计算 elapsed_secs 也就是上次 checkpoint 之后已经过了多久

      now = (pg_time_t) time(NULL);

      elapsed_secs = now - last_checkpoint_time;

        // 上一次 checkpoint 之后超过 CheckPointTimeout 都未触发过 checkpoint 则强制触发一次

      if (elapsed_secs >= CheckPointTimeout)

      {

            // 如果没有外部 checkpoint 请求则记录一下 PendingCheckpointerStats

         if (!do_checkpoint)

            PendingCheckpointerStats.timed_checkpoints++;

            // 设置需要进行 checkpoint

         do_checkpoint = true;

         flags |= CHECKPOINT_CAUSE_TIME;

      }



      if (do_checkpoint)

      {

         bool      ckpt_performed = false;

         bool      do_restartpoint;

         // 检查现在是否在 recovery 中

         do_restartpoint = RecoveryInProgress();

         // 针对 CheckpointerShmem->ckpt_lck 加锁(自旋)

         // 修改变量设置当前正在执行 checkpoint 标记

         SpinLockAcquire(&CheckpointerShmem->ckpt_lck);

         flags |= CheckpointerShmem->ckpt_flags;

         CheckpointerShmem->ckpt_flags = 0;

         CheckpointerShmem->ckpt_started++;

         // 解锁

         SpinLockRelease(&CheckpointerShmem->ckpt_lck);

         ConditionVariableBroadcast(&CheckpointerShmem->start_cv);

         if (flags & CHECKPOINT_END_OF_RECOVERY)

            do_restartpoint = false;

         // 如果本次 checkpoint 距离上次太近会产生告警日志

         if (!do_restartpoint && (flags & CHECKPOINT_CAUSE_XLOG) && elapsed_secs < CheckPointWarning)

            ereport(LOG,(errmsg_plural("checkpoints are occurring too frequently (%d second apart)",

                              "checkpoints are occurring too frequently (%d seconds apart)",

                              elapsed_secs,

                              elapsed_secs),

                   errhint("Consider increasing the configuration parameter \"max_wal_size\".")));

         // 下面检测 checkpoint 类型,读取需要从哪里开始执行 checkpoint

         ckpt_active = true;

            // 如果是恢复阶段,从 xlog 上获取当前恢复点开始执行 checkpoint

         if (do_restartpoint)

            ckpt_start_recptr = GetXLogReplayRecPtr(NULL);

            // 如果是普通checkpoint,从 xlog 记录插入位置开始执行 checkpoint

         else

            ckpt_start_recptr = GetInsertRecPtr();

         ckpt_start_time = now;

         ckpt_cached_elapsed = 0;

            // 如果普通 checkpoint

         if (!do_restartpoint)

         {

            CreateCheckPoint(flags);

            ckpt_performed = true;

         }

            // 如果是恢复阶段 checkpoint

         else

            ckpt_performed = CreateRestartPoint(flags);

         // 每次 checkpoint 后关闭 smgr 文件,这样避免对存储介质管理器的无限应用

         smgrcloseall();

         // 本次 checkpoint 完毕,解锁

         SpinLockAcquire(&CheckpointerShmem->ckpt_lck);

         CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started;

         SpinLockRelease(&CheckpointerShmem->ckpt_lck);

         ConditionVariableBroadcast(&CheckpointerShmem->done_cv);

            // 成功执行 checkpoint 记录本次执行的时间

         if (ckpt_performed)

         {

            last_checkpoint_time = now;

         }

         else

         {

            last_checkpoint_time = now - CheckPointTimeout + 15;

         }

         ckpt_active = false;

         HandleCheckpointerInterrupts();

      }



      CheckArchiveTimeout();

      /* 报告活动统计信息到收集器 */

      pgstat_report_checkpointer();

        /* 报告wal统计信息到收集器 */

      pgstat_report_wal(true);

      if (((volatile CheckpointerShmemStruct *) CheckpointerShmem)->ckpt_flags)

         continue;

      // 如果设置了任何检查点标记位,重新进行下一次循环去处理检查点而不是sleeping

      now = (pg_time_t) time(NULL);

      elapsed_secs = now - last_checkpoint_time;

      if (elapsed_secs >= CheckPointTimeout)

         continue;        /* no sleep for us ... */

      cur_timeout = CheckPointTimeout - elapsed_secs;

      if (XLogArchiveTimeout > 0 && !RecoveryInProgress())

      {

         elapsed_secs = now - last_xlog_switch_time;

         if (elapsed_secs >= XLogArchiveTimeout)

            continue;     /* no sleep for us ... */

         cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);

      }

      (void) WaitLatch(MyLatch,

                   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,

                   cur_timeout * 1000L /* convert to ms */ ,

                   WAIT_EVENT_CHECKPOINTER_MAIN);

   }

}

后台定时、或者客户端请求 checkpoint 操作时,调用真正执行检查点的函数如下是  xlog.c CreateCheckPoint(),大体流程将内存中的 page 脏数据进行持久化、同时将当前执行 checkPoint 点对应 xlog 记录 lsn 保存到控制文件中。这个函数实现了下面几个重要功能:

(1)脏页刷入

(2)删除旧WAL:创建检查点后此位置之前的WAL可以删除

(3)故障恢复起点。

(4)更新控制文件中检查点信息,记录本次 CheckPoint 信息崩溃恢复时以做判断。

(5)Checkpoint skipped机制,判断是否有提交,没有新的提交可以跳过 CheckPoint。

xlog.c CreateCheckPoint() 代码如下:

void CreateCheckPoint(int flags)

{

   bool      shutdown; // 是否在 shutdown 中

   CheckPoint checkPoint;

   XLogRecPtr recptr; // xlog 记当前录位置

   XLogSegNo  _logSegNo; // 日志段号

   XLogCtlInsert *Insert = &XLogCtl->Insert; // 用于 checkpoint 后更新控制文件

   uint32    freespace;

   XLogRecPtr PriorRedoPtr; // xlog 上一次重做位置

   XLogRecPtr curInsert;  // xlog 当前插入位置

   XLogRecPtr last_important_lsn; // 上一个重要的 lsn

   VirtualTransactionId *vxids; // 虚拟事务id

   int          nvxids;

   int          oldXLogAllowed = 0;

   if (flags & (CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_END_OF_RECOVERY))

      shutdown = true;

   else

      shutdown = false;

   if (RecoveryInProgress() && (flags & CHECKPOINT_END_OF_RECOVERY) == 0)

      elog(ERROR, "can't create a checkpoint during recovery");

   MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));

   CheckpointStats.ckpt_start_t = GetCurrentTimestamp();

   // 告知smgr准备好,要开始建检查点了

   SyncPreCheckpoint();

   START_CRIT_SECTION();

    // 如果是 shutdown 时执行 checkpoint 更新一下控制文件

   if (shutdown)

   {

      LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);

      ControlFile->state = DB_SHUTDOWNING;

      UpdateControlFile();

      LWLockRelease(ControlFileLock);

   }

   MemSet(&checkPoint, 0, sizeof(checkPoint));

   checkPoint.time = (pg_time_t) time(NULL);

   if (!shutdown && XLogStandbyInfoActive())

      checkPoint.oldestActiveXid = GetOldestActiveTransactionId();

   else

      checkPoint.oldestActiveXid = InvalidTransactionId;

    // 获取 wal 中上一个 xlog 插入位置,CheckPoint之后需要从这里继续插入新的 CheckPoint-xlog

   last_important_lsn = GetLastImportantRecPtr();

   WALInsertLockAcquireExclusive();

   curInsert = XLogBytePosToRecPtr(Insert->CurrBytePos);

   if ((flags & (CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_END_OF_RECOVERY |

              CHECKPOINT_FORCE)) == 0)

   {

      if (last_important_lsn == ControlFile->checkPoint)

      {

         WALInsertLockRelease();

         END_CRIT_SECTION();

         ereport(DEBUG1,

               (errmsg_internal("checkpoint skipped because system is idle")));

         return;

      }

   }

   if (flags & CHECKPOINT_END_OF_RECOVERY)

      oldXLogAllowed = LocalSetXLogInsertAllowed();

   checkPoint.ThisTimeLineID = XLogCtl->InsertTimeLineID;

   if (flags & CHECKPOINT_END_OF_RECOVERY)

      checkPoint.PrevTimeLineID = XLogCtl->PrevTimeLineID;

   else

      checkPoint.PrevTimeLineID = checkPoint.ThisTimeLineID;

   checkPoint.fullPageWrites = Insert->fullPageWrites;

   freespace = INSERT_FREESPACE(curInsert);

   if (freespace == 0)

   {

      if (XLogSegmentOffset(curInsert, wal_segment_size) == 0)

         curInsert += SizeOfXLogLongPHD;

      else

         curInsert += SizeOfXLogShortPHD;

   }

   checkPoint.redo = curInsert;

   RedoRecPtr = XLogCtl->Insert.RedoRecPtr = checkPoint.redo;

   WALInsertLockRelease();

   SpinLockAcquire(&XLogCtl->info_lck);

   XLogCtl->RedoRecPtr = checkPoint.redo;

   SpinLockRelease(&XLogCtl->info_lck);

   if (log_checkpoints)

      LogCheckpointStart(flags, false);

   update_checkpoint_display(flags, false, false);

   TRACE_POSTGRESQL_CHECKPOINT_START(flags);

   LWLockAcquire(XidGenLock, LW_SHARED);

   checkPoint.nextXid = ShmemVariableCache->nextXid;

   checkPoint.oldestXid = ShmemVariableCache->oldestXid;

   checkPoint.oldestXidDB = ShmemVariableCache->oldestXidDB;

   LWLockRelease(XidGenLock);

   LWLockAcquire(CommitTsLock, LW_SHARED);

   checkPoint.oldestCommitTsXid = ShmemVariableCache->oldestCommitTsXid;

   checkPoint.newestCommitTsXid = ShmemVariableCache->newestCommitTsXid;

   LWLockRelease(CommitTsLock);

   LWLockAcquire(OidGenLock, LW_SHARED);

   checkPoint.nextOid = ShmemVariableCache->nextOid;

   if (!shutdown)

      checkPoint.nextOid += ShmemVariableCache->oidCount;

   LWLockRelease(OidGenLock);

   MultiXactGetCheckptMulti(shutdown,

                      &checkPoint.nextMulti,

                      &checkPoint.nextMultiOffset,

                      &checkPoint.oldestMulti,

                      &checkPoint.oldestMultiDB);

   END_CRIT_SECTION();

   vxids = GetVirtualXIDsDelayingChkpt(&nvxids, DELAY_CHKPT_START);

   if (nvxids > 0)

   {

      do

      {

         pg_usleep(10000L); /* wait for 10 msec */

      } while (HaveVirtualXIDsDelayingChkpt(vxids, nvxids,

                                   DELAY_CHKPT_START));

   }

   pfree(vxids);

    // 执行 ShardBuffer 中数据写入磁盘

   CheckPointGuts(checkPoint.redo, flags);

   vxids = GetVirtualXIDsDelayingChkpt(&nvxids, DELAY_CHKPT_COMPLETE);

   if (nvxids > 0)

   {

      do

      {

         pg_usleep(10000L); /* wait for 10 msec */

      } while (HaveVirtualXIDsDelayingChkpt(vxids, nvxids,

                                   DELAY_CHKPT_COMPLETE));

   }

   pfree(vxids);

   if (!shutdown && XLogStandbyInfoActive())

      LogStandbySnapshot();

   START_CRIT_SECTION();

   // 写入一条 CheckPoint Start 的 xlog

   XLogBeginInsert();

   XLogRegisterData((char *) (&checkPoint), sizeof(checkPoint));

   recptr = XLogInsert(RM_XLOG_ID,

                  shutdown ? XLOG_CHECKPOINT_SHUTDOWN :

                  XLOG_CHECKPOINT_ONLINE);

   XLogFlush(recptr);

   if (shutdown)

   {

      if (flags & CHECKPOINT_END_OF_RECOVERY)

         LocalXLogInsertAllowed = oldXLogAllowed;

      else

         LocalXLogInsertAllowed = 0; /* never again write WAL */

   }

   if (shutdown && checkPoint.redo != ProcLastRecPtr)

      ereport(PANIC,

            (errmsg("concurrent write-ahead log activity while database system is shutting down")));

   // 修改控制文件变量记录本次 CheckPoint 对应的 xlog

   PriorRedoPtr = ControlFile->checkPointCopy.redo;

   LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);

   if (shutdown)

      ControlFile->state = DB_SHUTDOWNED;

   ControlFile->checkPoint = ProcLastRecPtr;

   ControlFile->checkPointCopy = checkPoint;

   /* crash recovery should always recover to the end of WAL */

   ControlFile->minRecoveryPoint = InvalidXLogRecPtr;

   ControlFile->minRecoveryPointTLI = 0;

   SpinLockAcquire(&XLogCtl->ulsn_lck);

   ControlFile->unloggedLSN = XLogCtl->unloggedLSN;

   SpinLockRelease(&XLogCtl->ulsn_lck);

    // 更新控制文件

   UpdateControlFile();

   LWLockRelease(ControlFileLock);

   /* Update shared-memory copy of checkpoint XID/epoch */

   SpinLockAcquire(&XLogCtl->info_lck);

   XLogCtl->ckptFullXid = checkPoint.nextXid;

   SpinLockRelease(&XLogCtl->info_lck);

   END_CRIT_SECTION();

   SyncPostCheckpoint();

   // 估算本次 CheckPoint 和上次的距离,也就是中间包含多上 xlog 量

   if (PriorRedoPtr != InvalidXLogRecPtr)

      UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);

   // 获取日志段号,计算执行需要保留的 xlog 的 LSN

   XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);

    // 据max_slot_wal_keep_size和wal_keep_size两个参数设置,再次调整最旧的需要保留的_logSegNo

   KeepLogSeg(recptr, &_logSegNo);

   if (InvalidateObsoleteReplicationSlots(_logSegNo))

   {

      XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);

      KeepLogSeg(recptr, &_logSegNo);

   }

    // 从这个 xlog 往前一条开始就是可以删除的 xlog 了

   _logSegNo--;

    // 删除无用 xlog

   RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr,checkPoint.ThisTimeLineID);

   if (!shutdown)

      PreallocXlogFiles(recptr, checkPoint.ThisTimeLineID);

   if (!RecoveryInProgress())

      TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());

   LogCheckpointEnd(false);

   update_checkpoint_display(flags, false, true);

   TRACE_POSTGRESQL_CHECKPOINT_DONE(CheckpointStats.ckpt_bufs_written,

                            NBuffers,

                            CheckpointStats.ckpt_segs_added,

                            CheckpointStats.ckpt_segs_removed,

                            CheckpointStats.ckpt_segs_recycled);

}

接下来将 ShardBuffer 缓存数据写入磁盘,xlog.c CheckPointGuts() 调用 bufmgr.c CheckPointBuffers() 就是将内存页进行 flush。

会遍历所有 Buffer,判断是否是脏页 BM_DIRTY,如果是脏页则标记为 BM_CHECKPOINT_NEEDED 需要 flush,然后对这些 Page 进行排序减少随机IO,最后调用  SyncOneBuffer() 一次一个 Page 进行写入。

bufmgr.c BufferSync() 代码如下:

static void BufferSync(int flags)

{

           // 省略

    // 遍历缓存,使用 BM_CHECKPOINT_NEEDED 标记需要写入的 page.

    // 计数值保存在 mun_to_scan 中用来估计工作量

   num_to_scan = 0;

   for (buf_id = 0; buf_id < NBuffers; buf_id++)

   {

        // 获取 buffer 的 desc

      BufferDesc *bufHdr = GetBufferDescriptor(buf_id);

        // 给 buffer 加入自旋锁

      buf_state = LockBufHdr(bufHdr);

        // mask = BM_DIRTY 也就是脏页

      if ((buf_state & mask) == mask)

      {

         CkptSortItem *item;

            // 标记为需要 flush

         buf_state |= BM_CHECKPOINT_NEEDED;

         item = &CkptBufferIds[num_to_scan++];

         item->buf_id = buf_id;

         item->tsId = bufHdr->tag.spcOid;

         item->relNumber = BufTagGetRelNumber(&bufHdr->tag);

         item->forkNum = BufTagGetForkNum(&bufHdr->tag);

         item->blockNum = bufHdr->tag.blockNum;

      }

      UnlockBufHdr(bufHdr, buf_state);

      if (ProcSignalBarrierPending)

         ProcessProcSignalBarrier();

   }
   if (num_to_scan == 0)

      return;                /* nothing to do */

   WritebackContextInit(&wb_context, &checkpoint_flush_after);
   TRACE_POSTGRESQL_BUFFER_SYNC_START(NBuffers, num_to_scan);
   // 对需要 flush 的 page 进行排序减少随机磁盘IO
   sort_checkpoint_bufferids(CkptBufferIds, num_to_scan);
   num_spaces = 0;
   // 为每一个需要刷脏页的表空间分配进度状态.
   last_tsid = InvalidOid;
   for (i = 0; i < num_to_scan; i++)
   {
      //  省略

   }
   // 在单个标记的写进度上构建最小堆,并计算单个处理缓冲区占比多少.
   ts_heap = binaryheap_allocate(num_spaces,ts_ckpt_progress_comparator,NULL);
   for (i = 0; i < num_spaces; i++)

   {
      CkptTsStatus *ts_stat = &per_ts_stat[i];
      ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan;
      binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat));
   }
   binaryheap_build(ts_heap);
   // 迭代处理to-be-checkpointed buffers,刷脏页.循环调用 SyncOneBuffer 一处处理一个 page
   num_processed = 0;
   num_written = 0;
   while (!binaryheap_empty(ts_heap))

   {

      BufferDesc *bufHdr = NULL;
      CkptTsStatus *ts_stat = (CkptTsStatus *)
      DatumGetPointer(binaryheap_first(ts_heap));
      buf_id = CkptBufferIds[ts_stat->index].buf_id;
      Assert(buf_id != -1);
      bufHdr = GetBufferDescriptor(buf_id);
      num_processed++;

      if (pg_atomic_read_u32(&bufHdr->state) & BM_CHECKPOINT_NEEDED)

      {

            // 一次处理一个 page 的写入

         if (SyncOneBuffer(buf_id, false, &wb_context) & BUF_WRITTEN)

         {

            TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);

            PendingCheckpointerStats.buf_written_checkpoints++;

            num_written++;

         }

      }

      CheckpointWriteDelay(flags, (double) num_processed / num_to_scan);

   }

        // 省略

}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值