代码改变世界

PostgreSQL的WAL(3)--Checkpoint

2020-09-19 11:26  abce  阅读(1438)  评论(0编辑  收藏  举报

我们已经熟悉了buffer cache的结构(共享内存的主要对象之一),并得出结论,要在所有RAM内容丢失后发生故障后恢复,必须保留预写日志(WAL)。

我们上次中断的地方尚未解决的问题是,我们不知道在恢复期间从哪里开始播放WAL记录。从头开始,这是不可行的:不可能从服务器启动时保留所有WAL记录-这可能既需要巨大的内存,又要很长的恢复时间。我们需要找到一个点,并且可以从该位置开始恢复(并相应地安全删除所有先前的WAL记录)。这就是我们要讲的检查点。

检查点

检查点必须具备哪些特点呢? 我们必须确保所有从检查点开始的WAL记录都将应用于刷新到磁盘的页面。如果不是这种情况,则在恢复期间,我们可能从磁盘上读取一个过旧的页面版本,对其应用WAL记录,这样做会不可逆转地损害数据。

我们如何获得检查点? 最简单的选择是不时暂停系统工作,并将缓冲区的所有脏页和其他高速缓存刷新到磁盘。(请注意,仅写入页面,而不从高速缓存中逐出页面)这些点将满足上述条件,但是时而连续"死亡"一段时间的系统,没有人会满意。

实际上这有点复杂:检查点从一个点变成一个间隔。首先,我们启动一个检查点。之后,我们悄悄地将脏缓冲区刷新到磁盘上,而不会中断工作或在任何可能的情况下导致峰值负载。

 

当所有在检查点开始时变脏的缓冲区都在磁盘上时,该检查点被视为已完成。现在(但不是更早),我们可以使用开始时间作为开始恢复的时间。而且我们不再需要到现在为止创建的WAL记录。

 

一个被称作检查点进程的后台进程执行检查点。

写入脏缓冲区的持续时间由checkpoint_completion_target参数定义。它显示了写入完成后两个相邻检查点之间的时间比例。默认值为0.5(如上图所示),即两次检查之间的写入时间占一半。通常,此值增加到1.0,以实现更高的均匀性。

让我们更详细地看看执行检查点时会发生什么。

首先,检查点将XACT缓冲区刷新到磁盘。由于它们很少(只有128个),因此它们会立即被写入。

然后,主要任务开始:从缓冲区高速缓存中刷新脏页。正如我们已经提到的,由于缓冲区高速缓存的大小可能很大,因此无法立即刷新所有页面。因此,buffer cache中所有当前脏的页面都用位于header中的特殊标志标记。

 

然后,检查点进程遍历所有缓冲区,并将标记的缓冲区刷新到磁盘。这里需要提醒你,页面不会从高速缓存中逐出,而只会写入磁盘。因此,你不必关注缓冲区的使用计数或是否被pin。

自然,在执行检查点时,buffer cache中的页面仍会继续被更新。但是不会标记新的脏缓冲区,并且检查点进程不会将它们写入磁盘。

 

在工作结束时,该过程将创建检查点末尾的WAL记录。该记录包含检查点开始时间的LSN。由于检查点启动时不会向WAL写入任何内容,因此任何日志记录都可以位于此LSN上。

此外,最后完成的检查点的指示在$PGDATA/global/pg_control文件中更新。在检查点完成之前,pg_control指向上一个检查点。

 

为了观看检查点的工作,让我们创建一个表。它的页面将进入buffer cache并称为脏页:

=> CREATE TABLE chkpt AS SELECT * FROM generate_series(1,10000) AS g(n);
=> CREATE EXTENSION pg_buffercache;
=> SELECT count(*) FROM pg_buffercache WHERE isdirty;
 count
-------
    78
(1 row)

让我们记住当前的WAL位置:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A048
(1 row)

现在,让我们手动执行检查点,以确保高速缓存中不留任何脏页(正如我们已经提到的,可以出现新的脏页,但是在上述情况下,执行检查点时没有更改):

=> CHECKPOINT;
=> SELECT count(*) FROM pg_buffercache WHERE isdirty;
 count
-------
     0
(1 row)

让我们看一下检查点在WAL中的体现:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A0E4
(1 row)

 

postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/3514A048 -e 0/3514A0E4
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 0/3514A048, prev 0/35149CEC, desc: RUNNING_XACTS nextXid 101105 latestCompletedXid 101104 oldestRunningXid 101105
rmgr: XLOG        len (rec/tot):    102/   102, tx:          0, lsn: 0/3514A07C, prev 0/3514A048, desc: CHECKPOINT_ONLINE redo 0/3514A048; tli 1; prev tli 1; fpw true; xid 0:101105; oid 74081; multi 1; offset 0; oldest xid 561 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 101105; online

在这里看到两个记录。最后一个是检查点完成的记录(CHECKPOINT_ONLINE)。在单词“ redo”之后输出检查点开始的LSN,此位置对应于在检查点开始时间的最后一个WAL记录。

我们将在控制文件中找到相同的信息:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | egrep 'Latest.*location'
Latest checkpoint location:           0/3514A07C
Latest checkpoint's REDO location:    0/3514A048

恢复

现在,我们准备更准确地陈述上一篇文章中提及的恢复算法。

如果postgresql server出现故障,则在随后的启动中,启动过程会通过查看pg_control文件来查找与“shutdown”状态不同的状态。在这种情况下,将执行自动恢复。

首先,恢复过程将从pg_control文件读取检查点开始位置。(要完成此操作,如果backup_label文件可用,那么将从那里读取检查点记录-从备份中还原这是必需的,但这是另一个系列的主题。)

然后,恢复过程将从找到的位置开始读取WAL,并将WAL记录逐一应用于页面(如果有需要,正如我们上次讨论的那样)。

最后,all unlogged tables are emptied by their initialization forks.。

这是启动过程完成的工作,检查点进程立即执行检查点以保护磁盘上已还原的状态。

我们可以通过强制在immediate模式下关闭来模拟故障。

student$ sudo pg_ctlcluster 11 main stop -m immediate --skip-systemctl-redirect

(这里需要--skip-systemctl-redirect选项,因为我们使用安装在Ubuntu上的PostgreSQL。它由pg_ctlcluster命令控制,该命令实际上调用systemctl,而后者又调用pg_ctl。但是--skip-systemctl-redirect选项使我们无需systemctl即可执行操作并保留重要信息。)

让我们检查集群的状态:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state:               in production

启动时,PostgreSQL知道发生了故障,需要恢复。

student$ sudo pg_ctlcluster 11 main start

postgres$ tail -n 7 /var/log/postgresql/postgresql-11-main.log
2019-07-17 15:27:49.441 MSK [8865] LOG:  database system was interrupted; last known up at 2019-07-17 15:27:48 MSK
2019-07-17 15:27:49.801 MSK [8865] LOG:  database system was not properly shut down; automatic recovery in progress
2019-07-17 15:27:49.804 MSK [8865] LOG:  redo starts at 0/3514A048
2019-07-17 15:27:49.804 MSK [8865] LOG:  invalid record length at 0/3514A0E4: wanted 24, got 0
2019-07-17 15:27:49.804 MSK [8865] LOG:  redo done at 0/3514A07C
2019-07-17 15:27:49.824 MSK [8864] LOG:  database system is ready to accept connections
2019-07-17 15:27:50.409 MSK [8872] [unknown]@[unknown] LOG:  incomplete startup packet

日志中报告了需要恢复:数据库系统未正确关闭; 自动恢复正在进行中 然后在«redo starts at»位置开始播放WAL记录,并在可能获取下一个WAL记录的同时继续播放。这样就可以在«redo done at»位置完成恢复,并且DBMS开始与客户端一起工作(数据库系统已准备好接受连接)。

在服务器正常关闭时会发生什么? 要将脏页刷新到磁盘,PostgreSQL断开所有客户端的连接,然后执行最终检查点。

让我们记住当前的WAL位置:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A14C
(1 row)

现在,我们以常规方式关闭:

student$ sudo pg_ctlcluster 11 main stop

检查集群状态:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state:               shut down

而且WAL具有最终检查点(CHECKPOINT_SHUTDOWN)的唯一记录:

postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/3514A14C
rmgr: XLOG        len (rec/tot):    102/   102, tx:          0, lsn: 0/3514A14C, prev 0/3514A0E4, desc: CHECKPOINT_SHUTDOWN redo 0/3514A14C; tli 1; prev tli 1; fpw true; xid 0:101105; oid 74081; multi 1; offset 0; oldest xid 561 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
pg_waldump: FATAL:  error in WAL record at 0/3514A14C: invalid record length at 0/3514A1B4: wanted 24, got 0

重新启动实例:

student$ sudo pg_ctlcluster 11 main start

后台写

如我们所知,检查点是将脏页从buffer cache刷新到磁盘的过程之一。但这不是唯一的。

如果后端进程需要从缓冲区刷新页面,但是该页面含有脏数据,则该进程将不得不自行将页面写入磁盘。这种情况并不好,因为它需要等待-在后台异步完成写入会更好。

因此,除了检查点进程外,还存在后台写进程(也称为bgwriter或仅称为writer)。该进程使用与驱逐技术相同的算法来搜索缓冲区。他们有两个区别。

1.后台写进程使用自己的指针,而不是指向«next victim»的指针。自己的指正可以在«next victim»的指针之前,但是永远不能在它之后。 2.遍历缓冲区时,使用计数不会减少。

 

被写出的buffer要满足以下条件:

·包含脏的数据

·没有被pin住(pin计数为0)

·使用计数为0

 

因此,后台写过程先于eviction,找到很可能很快被逐出的缓冲区。理想的情况是,后台写必须能够检测到他们选择的缓冲区可以被使用,而不会浪费写入时间。

调优

通常根据以下推理来设置检查点。

首先,我们需要确定在两个检查点之间可以负担多少数量的WAL记录(以及我们可以接受的恢复时间)。越多越好,但是出于明显的原因,该值是有限的。

然后,我们可以计算出在正常负载下生成此数量的wal所需的时间。我们已经讨论了如何执行此操作(我们需要记住WAL中的位置,并从另一个位置中减去一个)。

接着是检查点之间的通常间隔。设置checkpoint_timeout参数。默认值为5分钟,显然太短;通常会增加到半个小时。

但是有可能(甚至可能)有时负载会比平时更高,并且在参数指定的时间内会生成过多的WAL记录。在这种情况下,希望更频繁地执行检查点。为此,我们在max_wal_size参数中指定允许的WAL文件大小。如果实际量更多,则服务器将启动计划外的检查点。

服务器需要保留从最后一个完成的检查点开始的WAL文件以及当前检查点期间累积的文件。因此,可以将总数量估算为一个检查点周期中的数量乘以(1 + checkpoint_completion_target)。在版本11之前,我们应该乘以(2 + checkpoint_completion_target),因为PostgreSQL还保留了最后一个检查点中的文件。

因此,大多数检查点都按计划执行:每个checkpoint_timeout时间单位一次。但是在负载增加时,达到max_wal_size的数量时,检查点执行的频率会更高。

 

重要的是要理解可以超过max_wal_size参数的值:

·max_wal_size参数的值仅是理想值,而不是严格的限制。实际可能超过该值。

·server不能擦除尚未通过复制槽传递、或尚未归档的的wal文件,

 

可以通过min_wal_size参数指定最小值。

仅在调整检查点时才调整后台写才有意义。

后台写一次最多写bgwriter_lru_maxpages个页,在下一次写之前会根据bgwriter_delay的值sleep一段时间。

默认值为:bgwriter_delay = 200毫秒,bgwriter_lru_maxpages = 100。

如果根本找不到脏缓冲区(也就是说,系统中什么也没有发生),则它“进入休眠状态”。

监控

你需要根据监控结果来调优检查点进程和后台写。

如果wal数量太多,参数checkpoint_warning会输出警告提醒,默认值是30秒,我们需要将其调整到checkpoint_timeout的值。

参数log_checkpoints可以将检查点信息写入log。默认是不开启

=> ALTER SYSTEM SET log_checkpoints = on;
=> SELECT pg_reload_conf();

现在,让我们更改数据中的某些内容并执行检查点。

=> UPDATE chkpt SET n = n + 1;
=> CHECKPOINT;

在看看log文件的内容:

postgres$ tail -n 2 /var/log/postgresql/postgresql-11-main.log
2019-07-17 15:27:55.248 MSK [8962] LOG:  checkpoint starting: immediate force wait
2019-07-17 15:27:55.274 MSK [8962] LOG:  checkpoint complete: wrote 79 buffers (0.5%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.013 s, total=0.025 s; sync files=2, longest=0.011 s, average=0.006 s; distance=1645 kB, estimate=1645 kB

我们可以在此处看到写入了多少缓冲区,在检查点之后更改了WAL文件集,执行检查点花费了多长时间以及相邻检查点之间的距离(以字节为单位)。

但是,最有用的信息可能是pg_stat_bgwriter视图中检查点和后台写进程的统计信息。

=> SELECT * FROM pg_stat_bgwriter \gx
-[ RECORD 1 ]---------+------------------------------
checkpoints_timed     | 0
checkpoints_req       | 1
checkpoint_write_time | 1
checkpoint_sync_time  | 13
buffers_checkpoint    | 79
buffers_clean         | 0
maxwritten_clean      | 0
buffers_backend       | 42
buffers_backend_fsync | 0
buffers_alloc         | 363
stats_reset           | 2019-07-17 15:27:49.826414+03

其中:

·checkpoints_timed--按计划(到达checkpoint_timeout时)。

·checkpoints_req--按需(包括在达到max_wal_size时执行的检查)。该值越大,表明检查点发生的越频繁。

 

以下是有关写入页数的重要信息:

·buffers_checkpoint--通过检查点。

·buffers_backend--通过后端进程。

·buffers_clean--通过后台写进程。

 

在一个经过良好调整的系统中,buffers_backend的值必须小于buffers_checkpoint和buffers_clean的总和。

参数maxwrite_clean的值也将有助于调整后台写。它显示由于超出bgwriter_lru_maxpages的值而使进程停止了多少次。

可以在重置收集的统计信息:

=> SELECT pg_stat_reset_shared('bgwriter');

  

 

 

原文地址:https://habr.com/en/company/postgrespro/blog/494464/