受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

28.5. WAL 配置 #

有若干与 WAL 相关的配置参数会影响数据库性能。本节解释它们 的用法。有关设置服务器配置参数的一般信息,请参见 Chapter 19

检查点 是事务序列中的一些点,在这些点上可以保证堆和索引数据文件已经用检查点之前写入 的全部信息进行了更新。执行检查点时,所有脏数据页都会刷写到磁盘,并向 WAL 文件写入一条特殊的检查点记录。(更改记录此前已经刷入 WAL 文件。)如果发生崩溃,崩溃恢复过程会查看最新的检查点记录,以确定应该从 WAL 的哪个位置(称为重做记录)开始执行 REDO。该点之前对数据文件所做的任何更改 都保证已经在磁盘上。因此,检查点之后,位于包含重做记录的段之前的 WAL 段不再需要, 可以被回收或删除。(若正在执行 WAL 归档,则这些 WAL 段必 须先归档,随后才能回收或删除。)

检查点要求将所有脏数据页刷盘,这可能造成可观的 I/O 负载。因此,检查点活动会 被节流,使 I/O 从检查点开始时就启动,并在下一个检查点预计开始前完成;这样可 将检查点期间的性能下降降到最低。

服务器的检查点进程会定期自动执行检查点。每经过 checkpoint_timeout 秒,或者当 max_wal_size 即将超出时,就会开始一个检查点,以先到者为 准。默认设置分别是 5 分钟和 1 GB。如果自上一个检查点以来没有写入任何 WAL, 那么即使 checkpoint_timeout 已经过去,也会跳过新的 检查点。(如果正在使用 WAL 归档,而你希望对文件归档频率设定下限,从而限制 潜在数据丢失,那么应当调整参数 archive_timeout,而不是检查点参数。) 也可以使用 SQL 命令 CHECKPOINT 强制执行检查点。

减小 checkpoint_timeout 和/或 max_wal_size 会让检查点更频繁地发生。这样可以加快崩溃后 恢复,因为需要重做的工作更少。不过,这必须与更频繁刷写脏数据页所增加的成本 权衡。如果设置了 full_page_writes(默认就是如此), 还要考虑另一个因素。为了保证数据页一致性,每个检查点之后对某个数据页的首次 修改,都会导致把整页内容写入日志。在这种情况下,更短的检查点间隔会增加输出到 WAL 的数据量,部分抵消缩短间隔的目的,并且无论如何都会带来更多磁盘 I/O。

检查点的代价相当高,首先是因为它要求写出当前所有脏缓冲区,其次是因为它还会像 上文所述那样带来额外的后续 WAL 流量。因此,明智的做法是将检查点参数设得足够 高,避免检查点发生得过于频繁。作为对检查点参数的一种简单合理性检查,你可以 设置参数 checkpoint_warning。如果检查点之间的间隔 小于 checkpoint_warning 秒,服务器日志就会输出一条消息, 建议增大 max_wal_size。偶尔出现这样的消息无需惊慌,但如果 它频繁出现,就应当增大检查点控制参数。大型 COPY 传输之类的 批量操作,如果 max_wal_size 设得不够高,也可能导致出现 多次此类警告。

为了避免大量页面写出在短时间内淹没 I/O 系统,检查点期间对脏缓冲区的写出会被 分散到一段时间内。这段时间由 checkpoint_completion_target 控制,它表示为检查点间隔 (通过 checkpoint_timeout 配置)的一个分数。系统会调节 I/O 速率,使检查点在给定比例的 checkpoint_timeout 秒数 经过时完成,或者在 max_wal_size 即将超出之前完成,以较早 者为准。使用默认值 0.9 时,可以预期 PostgreSQL 会在下一次计划检查点开始前稍早完成每个检查点(大约在检查点间隔的 90% 时)。 这样能尽可能摊平 I/O,从而使检查点 I/O 负载在整个检查点间隔内保持稳定。这样 做的缺点是,延长检查点会影响恢复时间,因为需要保留更多 WAL 段以备恢复时 使用。关心恢复耗时的用户,可能希望减小 checkpoint_timeout,让检查点更频繁地发生,但仍将 I/O 分散 在整个检查点间隔内。另一种办法是减小 checkpoint_completion_target,但这会导致检查点期间 I/O 更密集,而在检查点完成后、下一次计划检查点开始前 I/O 更少,因此不推荐。虽然 checkpoint_completion_target 理论上可以设到 1.0,但通常 建议不要超过 0.9(默认值),因为检查点除了写出脏缓冲区之外还包含其他活动。 设置为 1.0 很可能导致检查点不能按时完成,从而因为所需 WAL 段数量的意外波动而 损失性能。

在 Linux 和 POSIX 平台上,checkpoint_flush_after 允许在检查点写出的操作系统页面达到可配置的字节数后,强制把它们刷到磁盘。 否则,这些页面可能会停留在操作系统页缓存中,从而在检查点末尾发出 fsync 时引发停顿。这个设置通常有助于降低事务延迟,但也可能 对性能产生不利影响,尤其是在工作负载大于 shared_buffers、但小于操作系统页缓存时。

pg_wal 目录中 WAL 段文件的数量取决于 min_wal_sizemax_wal_size,以及此前几 个检查点周期中产生的 WAL 数量。当旧的 WAL 段文件不再需要时,它们会被删除或 回收(也就是重命名为编号序列中的未来段文件)。如果由于 WAL 输出速率短期突增而 超过了 max_wal_size,那么不再需要的段文件就会被删除, 直到系统重新回到该限制之下。在此限制以下,系统会回收足够多的 WAL 文件,以覆 盖到下一个检查点之前的预估需求,其余则删除。这个估计基于此前几个检查点周期中 所用 WAL 文件数量的移动平均值。如果实际使用量超过估计值,移动平均会立即增大, 因而在一定程度上能够适应峰值使用量,而不仅仅是平均使用量。 min_wal_size 规定了为将来复用而回收的 WAL 文件最小总量; 即使系统空闲,且 WAL 使用量估计表明只需要很少 WAL,也始终会回收这么多文件以 供未来使用。

无论 max_wal_size 如何设置,最近 wal_keep_size MB 的 WAL 文件外加一个额外的 WAL 文件 都会始终保留。此外,如果使用了 WAL 归档,那么旧段在完成归档之前不能被删除或 回收。如果 WAL 归档跟不上 WAL 产生的速度,或者 archive_command 反复失败,那么旧 WAL 文件就会在 pg_wal 中不断累积,直到问题解决。使用复制槽的备库 如果速度很慢或者已经失败,也会产生同样的效果(见 Section 26.2.6)。

在归档恢复或备库模式下,服务器会周期性地执行 重启点, 其行为类似于正常运行时的检查点:服务器会强制把自身状态写盘,更新 pg_control 文件,以表明已经处理过的 WAL 数据无需再次 扫描,然后回收 pg_wal 目录中的旧 WAL 段文件。重启点的 执行频率不会高于主库上的检查点,因为重启点只能在检查点记录处执行。重启点既 可能由调度触发,也可能由外部请求触发。当到达某条检查点记录且距离上次重启点 至少已经过去 checkpoint_timeout 秒,或者 WAL 大小即将超过 max_wal_size 时,就会触发重启点。不过,由于重启点的可执行时机受到 限制,恢复期间经常会超出 max_wal_size,最多可能超出一个 检查点周期对应的 WAL 量。(无论如何,max_wal_size 从来 都不是硬上限,因此你始终应留出充足余量,以免耗尽磁盘空间。)

有两个常用的内部 WAL 函数: XLogInsertRecordXLogFlushXLogInsertRecord 用于把新记录放入共享内存中的 WAL 缓冲区。如果新记录没有可用空间, XLogInsertRecord 就不得不把 若干已满的 WAL 缓冲区写出(移入内核缓存)。这并不理想,因为 XLogInsertRecord 会在每一次数据库底层修改(例如插入行) 时被调用,而在调用它时,受影响数据页上正持有排他锁,因此这一操作必须尽可能 快。更糟的是,写 WAL 缓冲区还可能迫使系统创建新的 WAL 段, 这会花费更多时间。正常情况下,WAL 缓冲区应由 XLogFlush 请求负责写出并刷盘; 该请求大多发生在事务提交时,以确保事务记录被刷入持久存储。在 WAL 输出量很高 的系统上,XLogFlush 请求可能不够频繁,无法避免 XLogInsertRecord 自己去执行写出。在这种系统上,应增加 WAL 缓冲区的数量,具体做法是修改参数 wal_buffers。当设置了 full_page_writes 且系统非常繁忙时,提高 wal_buffers 还有助于平滑每次检查点后紧接着那段时期的响应时间。

参数 commit_delay 定义了组提交的领导者进程在 XLogFlush 内部获取锁之后会睡眠多少微秒,在此期间,组提 交的追随者会排队跟在领导者后面。这一延迟允许其他服务器进程把自己的提交记录添 加到 WAL 缓冲区中,以便它们都能由领导者最终执行的同步操作一并刷出。如果没有 启用 fsync,或者当前处于活跃事务中的其他会话少于 commit_siblings 个,就不会发生睡眠;这样可以避免在其他 会话不太可能很快提交时还去睡眠。请注意,在某些平台上,休眠请求的分辨率是 10 毫秒,因此介于 1 到 10000 微秒之间的任何非零 commit_delay 设置,效果都相同。还要注意,在某些平台上, 休眠操作持续的时间可能会比该参数所请求的略长。

由于 commit_delay 的目的,是让每次刷盘操作的成本在并发 提交的事务之间分摊(代价是可能增加事务延迟),因此在选择该设置之前,有必要 先量化这一成本。该成本越高,commit_delay 在提高事务吞吐量 方面预计就越有效,当然这种效果也有上限。可以使用 pg_test_fsync 程序来测量单次 WAL 刷盘操作平均需要多少微秒。通常,把该程序报告的“单次 8kB 写操作之后再刷盘”的平均耗时取一半,往往是 commit_delay 最有效的设置,因此建议把这一值作为针对特定 工作负载进行优化时的起点。虽然在 WAL 位于高延迟旋转磁盘上时,调优 commit_delay 尤其有用,但即便是在同步时间非常快的存储 介质上,例如固态驱动器或带有电池后备写缓存的 RAID 阵列,其收益也可能很可观;不过 这无疑应当在有代表性的工作负载上进行测试。在这种场景下,通常应使用更高的 commit_siblings 值,而在高延迟介质上,较小的 commit_siblings 值往往更有帮助。还要注意, commit_delay 如果设得过高,也完全可能由于显著增加事务 延迟而导致总体事务吞吐量下降。

commit_delay 设为 0(默认值)时,仍然可能出现一种组 提交,但每个组只会包含那些恰好在前一次刷盘操作(若有)进行期间,到达“需要刷 出其提交记录”这一点的会话。客户端数量更高时,往往会出现一种 gangway effect,以至于即使 commit_delay 为 0,组提交的效果也会变得很显著,因此显式设置 commit_delay 的帮助反而会变小。只有在以下两种条件同时 满足时,设置 commit_delay 才可能有帮助:(1)存在一些 并发提交的事务;以及(2)吞吐量在某种程度上受限于提交速率。但在旋转延迟较高 的介质上,即使只有两个客户端(也就是一个正在提交的客户端,再加上一个并发的 兄弟事务),这个设置也能有效提高事务吞吐量。

参数 wal_sync_method 决定 PostgreSQL 将如何请求内核把 WAL 更新强制刷到磁盘。就可靠性而言,所有选项应当都是相同 的;例外是 fsync_writethrough,它有时能够在其他选项做不 到时强制刷新磁盘缓存。不过,具体哪个选项速度最快则高度依赖平台。可以使用 pg_test_fsync 程序测试不同选项的速度。请注意,如果 fsync 已被关闭,那么这个参数就没有意义。

启用配置参数 wal_debug(前提是 PostgreSQL 编译时支持它)会导致每一次 XLogInsertRecordXLogFlushWAL 调用都被记录到服务器日志中。将来,这个选项可能会 被更通用的机制所取代。

有两个内部函数负责把 WAL 数据写入磁盘: XLogWriteissue_xlog_fsync。 启用 track_wal_io_timing 后, XLogWrite 写 WAL 数据以及 issue_xlog_fsync 将 WAL 数据同步到磁盘所花费的总时间, 会分别统计为 wal_write_timewal_sync_time, 并分别记录在 pg_stat_wal 中。 XLogWrite 通常由 XLogInsertRecord (当 WAL 缓冲区没有空间容纳新记录时)、XLogFlush 以及 WAL 写入器调用,用于把 WAL 缓冲区写到磁盘并调用 issue_xlog_fsyncissue_xlog_fsync 通常由 XLogWrite 调用,用于将 WAL 文件同步到磁盘。如果 wal_sync_methodopen_datasyncopen_sync,那么 XLogWrite 中的写操作 本身就保证已将写出的 WAL 数据同步到磁盘,而 issue_xlog_fsync 什么也不做。如果 wal_sync_methodfdatasyncfsyncfsync_writethrough, 那么写操作会把 WAL 缓冲区移入内核缓存,而 issue_xlog_fsync 会把它们同步到磁盘。无论 track_wal_io_timing 是否开启, XLogWrite 写 WAL 数据的次数和 issue_xlog_fsync 同步 WAL 数据到磁盘的次数,也会分别统计 为 wal_writewal_sync,分别记录在 pg_stat_wal 中。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。