checkpoint是什么东西?一个位置?还是一个操作?
1、背景
WAL 日志就这么一直写下去,子子孙孙无穷匮也,磁盘不炸了吗
我怎么知道数据库什么时候崩的?恢复的时候,WAL 日志应该从哪开始重放呢?
checkpoint是什么东西?一个点吗?一个位置?还是一个操作?
2、checkpoint
checkpoint是一个操作,执行这个操作的开始时刻,会记录当前开始时刻的 WAL 位置作为重做点,这个位置会被保存在文件中。 然后将该重做点之前所有 shared buffer 中的脏页均被刷入到存储。checkpoint又名检查点,一般checkpoint会将某个时间点之前的脏数据全部刷新到磁盘,以实现数据的一致性与完整性。目前各个流行的关系型数据库都具备checkpoint功能,其主要目的是为了缩短崩溃恢复时间,以Oracle为例,在进行数据恢复时,会以最近的checkpoint为参考点执行事务前滚。PostgreSQL在崩溃恢复时会以最近的checkpoint为基础,不断应用这之后的WAL日志。
简单来说:
确保数据一致性:定期将内存中的脏页写入磁盘,确保数据库的一致性。
加速崩溃恢复:减少需要重放的 WAL 日志量,从而加速系统崩溃后的恢复过程。
控制 WAL 日志大小:通过记录检查点,限制 WAL 日志的增长,优化存储使用。
3、怎么触发checkpoint?
分析源码,发现在src/include/access/xlog.h
定义了几类触发的场景:
#define CHECKPOINT_IS_SHUTDOWN 0x0001 /* 数据库关闭 */
#define CHECKPOINT_END_OF_RECOVERY 0x0002 /* 数据库恢复结束时触发 */
#define CHECKPOINT_IMMEDIATE 0x0004 /* 立即执行检查点操作,不进行延迟 */
#define CHECKPOINT_FORCE 0x0008 /* 强制执行检查点 */
#define CHECKPOINT_FLUSH_ALL 0x0010 /* 刷新所有页面,包括未记录表的页面 */
/* 以下标志对 RequestCheckpoint 函数非常重要 */
#define CHECKPOINT_WAIT 0x0020 /* 等待检查点操作完成 */
#define CHECKPOINT_REQUESTED 0x0040 /* 已发出检查点请求 */
/* 以下标志指示检查点请求的原因 */
#define CHECKPOINT_CAUSE_XLOG 0x0080 /* XLOG日志量达到一定程度触发 */
#define CHECKPOINT_CAUSE_TIME 0x0100 /* 周期性触发 */
根据上述定义,也就是说,以下几种情况会触发PG数据库操作系统做检查点操作:
超级用户(其他用户不可)执行CHECKPOINT命令
数据库shutdown
数据库recovery完成
XLOG日志量达到了触发checkpoint阈值
周期性地进行checkpoint
需要刷新所有脏页
为了能够周期性的创建检查点,减少崩溃恢复时间,同时合并I/O,PostgreSQL提供了辅助进程checkpointer。它会对不断检测周期时间以及上面的XLOG日志量阈值是否达到,而周期时间以及XLOG日志量阈值可以通过参数来设置大小,接下来介绍下与checkpoints相关的参数。
checkpoint_segments
:WAL log的最大数量,系统默认值是3。超过该数量的WAL日志,会自动触发checkpoint。checkpoint_timeout
:系统自动执行checkpoint之间的最大时间间隔。系统默认值是5分钟。checkpoint_completion_target
:该参数表示checkpoint的完成时间占两次checkpoint时间间隔的比例,系统默认值是0.5,也就是说每个checkpoint需要在checkpoints间隔时间的50%内完成。checkpoint_warning
:系统默认值是30秒,如果checkpoints的实际发生间隔小于该参数,将会在server log中写入写入一条相关信息。可以通过设置为0禁用。
4、checkpoint 操作的过程
checkpoint操作执行的过程:
假设要执行三条insert语句
checkpoint 操作首先记录下 checkpoint 的开始位置,记录为 redo point(重做位点)
checkpoint 将 shared buffer 中的数据刷到磁盘里面去
这时候数据库又来了一条 SQL insert 3
checkpoint 刷脏结束,redo point 之前的数据均已被刷到磁盘存储(数据1和2)
每个检查点后,第一次数据页的变化会导致整个页面会被记录在XLOG日志中
这时候假如开始数据库恢复,那么数据库会从pg_control
文件中找到最新的 checkpoint 位置,再从 checkpoint 找到 redo point 的位置,开始重放日志。不难看出,1 和 2 这两个数据在 checkpoint 中已经持久化到磁盘存储,WAL 日志中也只有 INSERT 3 操作需要重放。ControlFileData
结构定义了pg_control
文件中存储的数据格式。这个结构位于src/include/catalog/pg_control.h
文件中。
typedef struct ControlFileData
{
uint32 system_identifier; /* 唯一的系统标识符,用于区分不同的数据库实例 */
uint32 pg_control_version; /* pg_control 文件的版本号 */
uint32 catalog_version_no; /* 数据库的目录版本号 */
DBState state; /* 数据库的当前状态 */
pg_time_t time; /* 最后一次更新的时间戳 */
XLogRecPtr checkPoint; /* 最后一个检查点的 WAL(Write-Ahead Logging)记录指针 */
XLogRecPtr prevCheckPoint; /* 上一个检查点的 WAL 记录指针 */
CheckPoint checkPointCopy; /* 最后一个检查点记录的副本 */
XLogRecPtr unloggedLSN; /* 当前未记录的 LSN(日志序列号) */
// 其他字段省略
} ControlFileData;
读取和写入 pg_control
文件的主要函数 ReadControlFile
、UpdateControlFile
位于src/backend/access/transam/xlog.c
文件中。查看源码,发现检查点机制的主要实现位于 src/backend/postmaster/checkpointer.c
文件中。以下是一些关键函数和它们的作用:CheckpointerMain
:检查点进程的主函数。
void
CheckpointerMain(void)
{
// 初始化和设置信号处理
pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SignalHandlerForShutdownRequest);
pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
pqsignal(SIGQUIT, SignalHandlerForCrashRequest);
pqsignal(SIGUSR1, SignalHandlerForCheckpointRequest);
pqsignal(SIGUSR2, SignalHandlerForCheckpointRequest);
// 主循环
for (;;)
{
// 等待检查点请求或超时
WaitLatchOrSocket(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, -1, WAIT_EVENT_CHECKPOINTER_MAIN);
// 处理检查点请求
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (shutdown_requested)
{
CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
proc_exit(0);
}
if (checkpoint_requested)
{
checkpoint_requested = false;
CreateCheckPoint(0);
}
}
}
CreateCheckPoint
:执行检查点操作的核心函数,负责将脏页写入磁盘并记录检查点信息。
void
CreateCheckPoint(int flags)
{
CheckPoint checkpoint;
XLogRecPtr recptr;
// 初始化检查点结构
MemSet(&checkpoint, 0, sizeof(CheckPoint));
// 获取当前 WAL 位置
recptr = GetInsertRecPtr();
// 将脏页写入磁盘
CheckPointBuffers(flags);
// 记录检查点信息到 WAL 日志
recptr = XLogInsert(RM_XLOG_ID, XLOG_CHECKPOINT_SHUTDOWN, &checkpoint);
// 更新控制文件
UpdateControlFile();
}
CheckPointBuffers
:将脏页写入磁盘。
void
CheckPointBuffers(int flags)
{
int num_to_write;
int num_written;
// 计算需要写入的脏页数量
num_to_write = GetNumDirtyBuffers();
// 写入脏页
num_written = WriteDirtyBuffers(num_to_write);
// 同步磁盘
if (num_written > 0)
SyncDataDirectory();
}
CheckPointWriteDelay
:控制写入速率,避免 I/O 峰值。
5、总结
checkpoint中记录了 redo point,标记 redo point 之前的数据均已刷脏,完成持久化存储
标记 redo point 之前的 WAL 日志可以被清理回收
每个检查点后,第一次数据页的变化会导致整个页面会被记录在XLOG日志中。
检查点的开销比较高,可以用checkpoint_warning自检,相应调大checkpoint_segments
检查点的位置保存在文件 pg_control,pg_control文件被损坏可能会导致数据库不可用### 数据库
评论