PostgreSQL中的WAL:3。检查点

我们已经熟悉了缓冲区高速缓存设备,它是共享内存中的主要对象之一,并意识到为了在RAM内容丢失时从故障中恢复,您需要保留一个预记录日志

我们最后一次停止的未解决问题是,您不知道何时可以在恢复期间开始回放日志。 从头开始,就像爱丽丝(Alice)的金所建议的那样,将无法正常工作:不可能从服务器启动存储所有日记帐分录-这可能是一个巨大的数目,而且恢复时间同样长。 我们需要一个逐步发展的起点,从该起点可以开始恢复(因此,我们可以安全地删除所有以前的日记帐分录)。 这是今天将要讨论的控制点

控制点


控制点应具有什么属性? 我们必须确保从检查点开始的所有日记帐分录都将应用于写入磁盘的页面。 如果不是这样,则在还原过程中,我们可以从磁盘上读取页面的旧版本并对其应用日记条目,从而不可避免地损坏数据。

如何获得断点? 最简单的选择是定期挂起系统,并将缓冲区和其他缓存的所有脏页刷新到磁盘。 (请注意,仅写入页面,而不从高速缓存中弹出页面。)这些点将满足条件,但是,当然,没有人会希望使用一个在无限期但非常重要的时间内持续冻结的系统。

因此,实际上,一切都有些复杂:控制点从一个点变成一个分段。 首先我们开始断点。 之后,在不中断工作的情况下(如果可能的话)在不创建峰值负载的情况下,我们将不干净的缓冲区缓慢转储到磁盘上。



当所有检查点开始时变脏的缓冲区均已写入时,该检查点被视为complete 。 现在(但不是更早),我们可以将起点用作开始恢复的起点。 到目前为止,我们不再需要日记帐分录。



该检查点由特殊的后台检查点过程处理。

脏缓冲区的持续时间由checkpoint_completion_target参数的值确定。 它显示了将在两个相邻控制点之间进行记录的时间。 默认值为0.5(如上图所示),即,记录花费控制点之间的时间的一半。 通常,该值增加到1.0以实现更大的均匀性。

让我们更详细地考虑执行控制点时会发生什么。

检查点进程首先将事务状态缓冲区(XACT)刷新到磁盘。 由于它们很少(总共128个),因此会立即进行记录。

然后开始主要工作-从缓冲区高速缓存中写入脏页。 正如我们已经说过的,因为缓冲区高速缓存的大小可能很大,所以不可能一次重置所有页面。 因此,首先,所有当前脏页在标头的缓冲区高速缓存中都用特殊标志标记。



然后,检查点过程逐渐遍历所有缓冲区,并将标记为磁盘的缓冲区刷新。 回想一下,页面不是从高速缓存中弹出的,而是仅写入磁盘的,因此您不必注意对缓冲区的调用次数或其固定。

带标签的缓冲区也可以由服务器进程写入-取决于谁先进入缓冲区。 在任何情况下,记录时都会删除先前设置的标志,因此(出于检查点的目的)缓冲区将仅写入一次。

自然,在执行检查点期间,页面继续在缓冲区高速缓存中更改。 但是,不会标记新的脏缓冲区,并且检查点进程不应将其写入。



在工作结束时,该过程将为检查点的结尾创建一个日记帐分录。 该记录包含控制点开始工作的LSN。 由于控制点在工作开始时并未向日志写入任何内容,因此该LSN可以包含任何日志记录。

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



要查看检查点的工作,请创建一些表-它的页面将进入缓冲区高速缓存,并且很脏:

=> 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) 

记住日志中的当前位置:

 => 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) 

让我们看看检查点如何反映在日志中:

 => 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,此位置对应于日记帐分录,该日记帐分录是检查点开始处的最后一个。

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

 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 

恢复


现在我们准备澄清上一篇文章中概述的恢复算法。

如果服务器崩溃,则下次启动时,启动过程将通过查看pg_control文件并查看“关闭”以外的状态来检测到此情况。 在这种情况下,将执行自动恢复。

首先,恢复过程将从相同的pg_control读取控制点开始的位置。 (为完整起见,我们注意到,如果存在backup_label文件,则将从中读取控制点的记录-这是从备份中还原所必需的,但这是一个单独的主题。)

然后,他将从找到的位置开始阅读杂志,然后将日记条目顺序应用于页面(如有必要,如我们上次讨论的)。

总之,所有未分类的表都将使用init文件中的图像覆盖。

此时,启动过程终止,检查点进程立即执行检查点以修复磁盘上的已还原状态。

您可以通过在即时模式下强制停止服务器来模拟故障。

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

(这里需要--skip-systemctl-redirect键,因为PostgreSQL是从软件包中安装的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 

消息日志中记录了恢复的需要: 数据库系统未正确关闭; 自动恢复进行中 。 然后,日记帐分录从“重做起始于”标记的位置开始播放,并继续直到可以检索下一个日记帐分录。 这样就完成了“重做完成”位置的恢复,并且DBMS开始与客户端一起工作( 数据库系统已准备好接受连接 )。

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

记住日志中的当前位置:

 => 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 

在日志中,我们找到了有关最终控制点的唯一记录(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 

(在一个可怕的致命消息中,pg_waldump只想说他读了杂志的末尾。)

再次运行实例。

 student$ sudo pg_ctlcluster 11 main start 

后台录音


我们发现,检查点是将脏页从缓冲区高速缓存写入磁盘的过程之一。 但不是唯一的。

如果后端需要将页面从缓冲区中推出,而页面又脏了,它将不得不将其自己写入磁盘。 这是一个糟糕的情况,导致期望-在后台异步进行记录时会更好。

因此,除了检查点过程之外,存在后台记录过程 (后台写入器,bgwriter或仅写入器)。 此过程使用与抢占机制相同的缓冲区搜索算法。 基本上有两个区别。

  1. 它不使用指向“下一个受害者”的指针,而是使用它自己的指针。 他可以领先于“受害者”的指针,但永远不会落后于他。
  2. 遍历缓冲区时,命中计数器不会减少。

缓冲区同时写入:

  • 包含更改的数据(脏),
  • 不固定(引脚数= 0),
  • 命中次数为零(使用计数= 0)。

因此,后台记录过程实际上是在排挤之前进行的,并发现了可能很快被排挤的缓冲区。 理想地,因此,服务进程应该发现可以使用它们选择的缓冲区而无需停止写入。

客制化


通常出于以下原因配置检查点过程

首先,您需要确定我们可以负担多少日志文件(以及适合我们的恢复时间)。 越大越好,但是出于明显的原因,该值将受到限制。

接下来,我们可以计算出在正常负载下该体积将生成多长时间。 我们已经考虑了如何执行此操作(我们需要记住日志中的位置,并从另一个位置中减去一个)。

这段时间将是我们在控制点之间的通常间隔。 我们将其写入checkpoint_timeout参数。 5分钟的默认值显然太小,通常将时间增加到半个小时。 我再说一遍:负担得起的里程碑越少越好,这会减少开销。

但是,有可能(甚至可能)有时负载会高于正常负载,并且在参数指定的时间内将生成过多的日记帐分录。 在这种情况下,我想更频繁地执行控制点。 为此,在max_wal_size参数中,我们指定在同一控制点内有效的数量。 如果获得更多的实际卷,则服务器将启动计划外的检查点。

因此,大多数控制点都按时间表进行:每个checkpoint_timeout时间单位一次。 但是随着负载的增加, 达到max_wal_size量时, 更频繁地调用控制点。

重要的是要了解, max_wal_size参数根本不会确定磁盘上的日志文件可以占用的最大数量。

  • 要从故障中恢复,您需要存储从最后一个检查点通过起的文件,以及在当前检查点操作期间累积的文件。 因此,总体积可以大致估算为
    (1 + checkpoint_completion_target )× max_wal_size
  • 在版本11之前,PostgreSQL还存储了已有两年历史的检查点的文件,因此在上式中的版本10之前,必须设置2而不是1。
  • max_wal_size参数只是一个愿望,而不是硬性限制。 结果可能更多。
  • 服务器无权清除尚未通过复制插槽传输的日志文件,以及在连续归档过程中尚未归档的日志文件。 如果使用此功能,则必须进行持续监视,因为这很容易使服务器内存溢出。

要完成图片,您不仅可以设置最大音量,还可以设置最小值:参数min_wal_size 。 此设置的含义是,服务器不会删除文件,但它们会被放入min_wal_size的卷中,而只是重命名它们并再次使用它们。 通过不断创建和删除文件,可以为您节省一些时间。

配置检查点后,可以进行后台记录过程 。 这些进程在一起,必须有时间写脏缓冲区,然后维护进程才需要它们。

后台记录过程最多以bgwriter_lru_maxpages页的周期运行,在bgwriter_delay的周期之间进入睡眠状态

一个工作周期中将记录的页面数由上次运行以来维修过程所请求的平均缓冲区数确定(使用移动平均值来消除运行之间的不均匀性,但不取决于悠久的历史)。 计算出的缓冲区数量乘以系数bgwriter_lru_multiplier (但无论如何不会超过bgwriter_lru_maxpages )。

默认值: bgwriter_delay = 200ms(很可能太多,它在1/5秒内会漏水 ), bgwriter_lru_maxpages = 100, bgwriter_lru_multiplier = 2.0(我们尝试提前响应需求)。

如果该进程根本没有检测到脏缓冲区(也就是说,系统中什么也没有发生),则它将“休眠”,从中可以推断出服务器进程正在访问该缓冲区。 之后,该过程将唤醒,并以通常的方式再次运行。

监控方式


可以并且应该调整控制点和背景记录设置,以接收监视的反馈。

如果由日志文件大小溢出引起的检查点运行太频繁,则checkpoint_warning参数显示警告。 其默认值为30秒,并且必须与checkpoint_timeout的值保持一致。

log_checkpoints参数(默认情况下禁用)允许在服务器消息日志中接收有关已执行检查点的信息。 打开它。

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

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

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

在消息日志中,我们将看到以下内容:

 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 

在这里,您可以看到写入了多少缓冲区,在控制点之后日志文件的组成如何变化,控制点花费了多长时间以及相邻控制点之间的距离(以字节为单位)。

但是,可能最有用的信息是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时)。

checkpoint_req的较大值(与checkpoints_timed相比)表明控制点的出现频率比预期的高。

有关已记录页数的重要信息:

  • buffers_checkpoint-检查点过程,
  • buffers_backend-通过服务进程,
  • buffers_clean-后台录制过程。

在一个调整良好的系统上,buffers_backend的值应大大小于buffers_checkpoint和buffers_clean的总和。

另外,maxwriter_clean对于设置后台记录很有用-此数字显示由于超出bgwriter_lru_maxpages而导致后台记录进程停止工作的次数

您可以使用以下调用重置累积的统计信息:

 => SELECT pg_stat_reset_shared('bgwriter'); 

待续

Source: https://habr.com/ru/post/zh-CN460423/


All Articles