说说pg中的检查点(checkpoint)之二

       前面讲了如何从外部来观察checkpoint,但是,实际上checkpoint内部是如何运行的呢?这里就有必要讲讲来龙去脉了。

       目前,PG中有一个单独的checkpoint的进程。一般来说,在正常的启动过程中,这个进程是postmaster进程生成的,实际上是一个信号的处理函数heaper()生成的,具体的调用关系如下:

 

main()->postmastermain()->reaper()->StartCheckpointer()->StartChildProcess()->AuxiliaryProcessMain()->CheckpointerMain()

 postmaster中触发heaper()的是一个子进程退出的信号,这个子进程的名字是startup process,具体这个进程干啥了大家可以去看看代码。

 

       CheckpointerMain()就是checkpoint进程的主体了。CheckpointerMain()主要分两部分:完成一些必要的初始化工作,之后就进入一个不断做checkpoint的死循环中。

  初始化包括三部分的内容:一,在CheckpointerShmem这个所有进程都可以访问的变量中设置checkpoint进程的PID,这个PID回头是其他进程通过kill函数来通知做checkpoint的必要参数;设置信号处理函数,其中一个信号处理函数是负责接收SIGINT信号来表示需要做checkpoint了;三,设置long jump的异常堆栈,使得出现ERROR时能跳转回来进行一场处理(也就是用C来简单实现了C++中异常捕获和处理)。

  在这个进程的死循环中,这个死循环的伪码如下:

 

for(;;){
     AbsorbFsyncRequests() // 整理fsync的请求
     if (got_SIGHUP)
     { 
         //用户执行了realod命令,重新加载配置问题
         ProcessConfigFile(PGC_SIGHUP);
         UpdateSharedMemoryConfig();
     }
     if (checkpoint_requested)
     {
         //接到信号,需要执行检查点
         checkpoint_requested = false;
         BgWriterStats.m_requested_checkpoints++;
         do_checkpoint = true;
     }
     if (shutdown_requested)
     {
        // 进程接到数据库退出的信号,退出
        ShutdownXLOG(0, 0);
        proc_exit(0);	
     }
     if (elapsed_secs >= CheckPointTimeout)
     {
           // 如果距离上次检查点发生的时间间隔大于checkpoint_timeout,则需要执行周期的checkpoint
           if (!do_checkpoint)
		BgWriterStats.m_timed_checkpoints++;
	   do_checkpoint = true;
	   flags |= CHECKPOINT_CAUSE_TIME;
     }
     if(do_checkpoint)
     {
          CreateCheckPoint(flags);//做检查点
          smgrcloseall() ;// 关闭所有的segments
     }
     CheckArchiveTimeout() //检查归档的timeout,切换WAL文件
     pgstat_send_bgwriter();//向pg_stat进程发生本次checkpoint的统计信息
   elapsed_secs = now - last_checkpoint_time;
     if (elapsed_secs >= CheckPointTimeout)//如果做检查点花费时间大于周期
         continue;
   elapsed_secs = now - last_xlog_switch_time;
   if (elapsed_secs >= XLogArchiveTimeout) // 如果大于归档的timeout
       continue;	
    WaitLatch()//睡眠,直至超时或是信号发生
}

       可以看到,能够影响发生做checkpoint基本上就两个原因:被其他进程要求执行(上一篇博客提到过),在一个就是超时了。

       而在这个循环中,有三个比较重要的函数:AbsorbFsyncRequests(),pgstat_send_bgwriter();CreateCheckPoint(flags)。

  AbsorbFsyncRequests()主要将CheckpointerShmem这个全局变量中fsync请求都拷贝过来,这些请求主要存在于CheckpointerShmem的requests字段中,这个字段是一个变长数组,长度就是block的块数。当bgwriter或是backend将一个block写回(write)物理文件后,会将对该文件的sync请求放入CheckpointerShmem的requests中。拷贝fsync请求之后,然后根据将每个请求塞入哈希表pendingOpsTable中,key就是由物理文件的rnode,将segno加入每个fork对应的位图中。简单来说,就是如果同一个segment上多个block需要fsync那么这些fsync会合并成对一个segment的fsync请求。所以这样就大大消除了fysnc的请求。这样的好处是,不到做检查点,pg是不会去主动fsync被写脏的segment的,只有到了检查点,才会主动去fsync。而在未发生checkpoint的这段时间内,操作系统会根据参数在后台fsync文件,这个需要配置dirty_ratio和dirty_background_ratio来控制OS后台fsync文件。

  而并不是只有checkpoint进程才会将写回的segment调用fsync的,backend进程也会调用,只不过是在系统压力很大的情况下才会发生:如果CheckpointerShmem的requests这个数组被塞满了,并且在将同一个segment的fysnc请求合并成一个之后,发现CheckpointerShmem的requests这个数组依然还是塞满了,那就说明系统需要fsync的segment的数量和share buffer中block的数量是一样的,这意味着什么呢?假设一个服务器给PG的share buffer是20G,那么出现这种情形,需要fsync回磁盘的数据超过 (20G/8K) *32M = 80T,如果真的发生了,说明系统的压力已经超出了硬件的承受能力了。

 当然 bgwriter也存在调用fsync数据块的可能,只不过这种可能性比前面说的这种可能性还要低好多,所以可以忽略。

  而pgstat_send_bgwriter()函数则是将每个checkpoint的统计信息发给pg_stat进程,统计信息存在如下的一个结构中:

 

typedef struct PgStat_MsgBgWriter
{
	PgStat_MsgHdr m_hdr;
	PgStat_Counter m_timed_checkpoints; //定期检查点次数
	PgStat_Counter m_requested_checkpoints; //请求执行的检查点次数
	PgStat_Counter m_buf_written_checkpoints; //检查点刷了多少脏block
	PgStat_Counter m_buf_written_clean;        //bgwriter刷了多少脏block
	PgStat_Counter m_maxwritten_clean;     //bgwriter刷脏块超过bgwriter_lru_maxpages的次数
	PgStat_Counter m_buf_written_backend; //backend刷回的脏块
	PgStat_Counter m_buf_fsync_backend;  //backend fsync的次数
	PgStat_Counter m_buf_alloc;                 // 重新分配的块数
	PgStat_Counter m_checkpoint_write_time;//检查点写文件花的时间
	PgStat_Counter m_checkpoint_sync_time; //检查点fsync文件花的时间
} PgStat_MsgBgWriter;

 这个数据结构基本上就对应我们前面说的pg_stat_bgwriter这个系统视图。checkpoint进程每次发生的统计信息只包括于本进程以及backend进程相关的信息:m_timed_checkpoints,m_requested_checkpoints,m_buf_written_checkpoints,m_buf_written_backend,m_buf_fsync_backend  m_checkpoint_write_time,m_checkpoint_sync_time。

 而m_buf_fsync_backend这个字段的值大于0,就说明前面说backend调用fsync的极端情况出现了,您该跟boss打报告研究下对策了。

 对于CreateCheckPoint(flags)这个函数,无疑是checkpoint进程的核心,下一篇将为您讲述。

 

<think>嗯,用户遇到了PostgreSQL从库启动时报错“invalid checkpoint record,could not locate a valid checkpoint record”,这个问题需要仔细分析可能的原因。首先,我应该回忆一下这个错误通常是什么引起的。根据经验,这个错误通常和流复制或主库备库之间的WAL日志不一致有关。 首先,主库和备库的PostgreSQL版本是否一致?如果版本不一致,可能会导致WAL记录格式不兼容,从而无法正确读取检查点记录。需要确认用户是否在搭建主从时检查过版本匹配情况。 接下来,检查流复制的配置是否正确。用户可能在配置recovery.conf或standby.signal文件时参数有误,例如primary_conninfo中的连接信息是否正确,确保备库能正确连接到主库。另外,restore_command是否设置正确,如果使用归档恢复,可能需要检查归档目录的权限和路径是否正确。 然后,考虑WAL日志的传输问题。主库的WAL日志是否成功传输到了备库?在流复制中,备库需要持续接收来自主库的WAL段。如果网络中断或者pg_hba.conf配置错误,可能导致传输中断,备库无法获取最新的检查点记录。此外,检查wal_keep_segments或wal_keep_size的设置是否足够,避免主库过早删除还未被备库接收的WAL文件。 还有一种可能是主库在某个检查点之后发生了重大变化,而备库的基础备份是在此之前进行的,导致备库无法找到有效的检查点。这时候可能需要重新做一个基础备份,使用pg_basebackup重新创建备库的数据目录,并确保备份完成后主库的WAL日志仍然保留足够的时间供备库同步。 另外,用户是否手动修改过备库的数据目录文件?比如误删除了某些WAL文件或者数据文件,也可能导致检查点记录无效。需要检查备库的pg_wal目录是否有完整的WAL文件,并确认没有人为的破坏。 此外,主库和备库之间的时间同步是否正常?如果时间不同步,可能导致某些基于时间戳的检查出现问题,但这种情况可能比较少见。 最后,如果以上步骤都确认无误,可能需要查看PostgreSQL的日志文件,寻找更详细的错误信息。主库和备库的日志可能会有更具体的线索,例如连接错误、权限问题或者文件损坏等。 总结下来,可能的解决步骤包括检查版本一致性、验证流复制配置、确保WAL日志传输正常、重新创建基础备份以及检查文件完整性等。需要引导用户一步步排查这些可能性,从而找到根本原因并解决问题。</think>PostgreSQL从库启动时出现`invalid checkpoint record, could not locate a valid checkpoint record`错误,通常**流复制配置**或**WAL日志完整性**有关。以下是逐步排查解决方法: --- ### **1. 基本原因分析** 此错误表明从库无法在主库传输的WAL日志中找到有效的检查点记录,可能原因包括: - 主从库PostgreSQL版本不一致 - 流复制参数配置错误 - WAL日志未正确传输(网络中断、权限问题) - 基础备份已过期或损坏 --- ### **2. 关键排查步骤** #### **步骤1:验证主从库版本一致性** - **检查版本号**: ```bash # 主库执行 psql -c "SELECT version();" # 从库执行(若可启动) psql -c "SELECT version();" ``` **要求**:主从库的PostgreSQL主版本号必须一致(如14.x14.x兼容,14.x15.x不兼容)。 --- #### **步骤2:检查流复制配置** - **从库的`postgresql.conf`**: - 确保`primary_conninfo`参数正确指向主库: ```ini primary_conninfo = 'host=<主库IP> port=5432 user=replication_user password=xxx' ``` - 检查`wal_level`是否为`replica`: ```ini wal_level = replica ``` - **从库的`standby.signal`文件**(PG12+)或`recovery.conf`(PG11及更早): - 确认文件存在且包含`standby_mode = on`(旧版本)。 --- #### **步骤3:验证WAL日志传输** - **主库检查WAL发送状态**: ```sql SELECT * FROM pg_stat_replication; ``` 观察从库是否在连接列表中,`state`应为`streaming`。 - **从库检查WAL接收状态**: ```sql SELECT * FROM pg_stat_wal_receiver; ``` 检查`status`是否正常,`received_lsn`是否持续更新。 --- #### **步骤4:检查WAL日志保留** - **主库的`wal_keep_size`设置**: ```ini wal_keep_size = 10GB # 确保足够保留未传输的WAL日志 ``` 如果主库过早删除未传输的WAL,从库会丢失连续性。 --- #### **步骤5:重建从库(终极解决方案)** 如果上述步骤无效,可能是基础备份已损坏,需重新创建从库: 1. **停止从库**: ```bash pg_ctl stop -D /path/to/standby/data ``` 2. **删除从库数据目录**: ```bash rm -rf /path/to/standby/data/* ``` 3. **重新生成基础备份**: ```bash pg_basebackup -h <主库IP> -U replication_user -D /path/to/standby/data -P -v -R ``` 4. **启动从库**: ```bash pg_ctl start -D /path/to/standby/data ``` --- ### **3. 附加注意事项** - **权限问题**:确保从库用户(如`replication_user`)在主库的`pg_hba.conf`中有REPLICATION权限。 - **网络连通性**:使用`telnet <主库IP> 5432`验证端口是否开放。 - **日志分析**:检查主从库的日志文件(`log_directory`)以获取更具体的错误信息。 --- ### **总结** 此错误通常由 **WAL日志连续性中断** 或 **配置错误** 导致。按顺序排查版本、配置、WAL传输状态,若仍无法解决,重建从库是最可靠的修复方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值