Postgresql 中文操作指南

30.5. WAL Configuration #

有几个与 WAL 相关的配置参数会影响数据库性能。本节解释了它们的使用方式。有关设置服务器配置参数的一般信息,请参见 Chapter 20

Checkpoints 是交易序列中的点,在该序列中,可以保证已使用该检查点之前写入的所有信息来更新堆和索引数据文件。在检查点时间,所有脏数据页面都被刷新到磁盘,并将特殊检查点记录写入 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(这是默认设置),则需要考虑另一个因素。为了确保数据页面的一致性,每次检查点后对数据页面的第一次修改都会导致记录整个页面内容。在这种情况下,较小的检查点间隔会增加 WRITABLE AHEAD LOG (WAL) 中的输出量,部分否定了使用较小间隔的目的,无论如何都会导致更多的磁盘 I/O。

检查点相当昂贵,首先是因为它们需要写入所有当前脏缓冲区,其次是因为它们会导致额外的后续 WAL 流量,如上所述。因此,明智的做法是将检查点参数设置得足够高,以避免检查点过于频繁。作为对检查点参数的简单健全性检查,可以设置 checkpoint_warning参数。如果检查点间隔小于_checkpoint_warning_秒,将向服务器日志输出一条消息,建议增加_max_wal_size_。偶尔出现这样的消息并不令人担忧,但如果经常出现,则应增加检查点控制参数。在还没有将_max_wal_size_设置得足够高的情况下进行大_COPY_传输等批量操作可能会导致出现这样的警告。

为了避免使用大量页面写入使 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,仍将总是回收大量 WAL 以供未来使用。

独立于_max_wal_size_,始终保留最新的 wal_keep_size兆字节 WAL 文件加上一个附加 WAL 文件。此外,如果使用 WAL 存档,则无法删除或回收旧段,直到它们被存档。如果 WAL 存档无法跟上 WAL 生成的速度,或者如果_archive_command_或_archive_library_反复失败,旧 WAL 文件将累积在_pg_wal_中,直到情况得到解决。使用复制槽的缓慢或失败的备用服务器将产生相同的效果(见 Section 27.2.6)。

在归档恢复或备用模式下,服务器会定期执行 restartpoints,这些操作类似于正常操作中的检查点:服务器会强制将所有状态刷新到磁盘,更新 pg_control 文件以指示不需要再次扫描已处理的 WAL 数据,然后回收 pg_wal 目录中的任何旧 WAL 段文件。重启点不能比主服务器上的检查点执行得更频繁,因为重启点只能在检查点记录上执行。当检查点记录已到达并且至少与上一个重启点之间过了 checkpoint_timeout 秒,或者当 WAL 大小即将超过 max_wal_size 时,就会触发重启点。但是,由于执行重启点的时间受到限制,因此在恢复过程中通常会超过 max_wal_size,最多可以超出一个检查点周期价值的 WAL。(反正 max_wal_size 永远不是一个硬性限制,因此你应该始终留出足够的裕量以避免用尽磁盘空间。)

有两个常用的内部 WAL 函数:XLogInsertRecord_和_XLogFlush。_XLogInsertRecord_用于将新记录放入共享内存中的 WAL 缓冲区。如果没有新记录的空间,_XLogInsertRecord_将不得不写入(移至内核缓存)一些已满的 WAL 缓冲区。这是不希望的,因为_XLogInsertRecord_用于每次数据库低级修改(例如,行插入)时,而此时正在对受影响的数据页面保持排他锁,因此该操作需要尽可能快。更糟的是,写入 WAL 缓冲区也可能强制创建一个新的 WAL 段,这需要更多时间。通常,WAL 缓冲区应由_XLogFlush_请求写入和刷新,该请求在大部分情况下在事务提交时发出,以确保事务记录刷新到永久存储中。在 WAL 输出较高的系统上,_XLogFlush_请求可能无法足够频繁地发生以防止_XLogInsertRecord_不得不进行写入。在这样的系统上,应该通过修改 wal_buffers参数来增加 WAL 缓冲区数量。当 full_page_writes设置并且系统非常繁忙时,将_wal_buffers_设置为较高值将有助于在每个检查点之后的立即期间平滑响应时间。

commit_delay参数定义在_XLogFlush_中获取锁后,组提交领导者进程将在微秒内休眠多长时间,而组提交跟随

由于 commit_delay 的目的是让每个刷新操作的成本在并发提交事务上摊销(可能以事务延迟为代价),因此在选择设置之前,有必要量化该成本。成本越高, commit_delay 在提高事务处理吞吐量方面的效果预期就越大,但要达到一定程度。 pg_test_fsync 程序可用于测量单个 WAL 刷新操作需要花费的平均时间(微秒)。程序报告的刷新时间平均值的一半(在进行 8kB 单次写入操作后)通常是 commit_delay 最有效的设置,因此建议将其用作针对特定工作负载优化时的起始点。虽然在 WAL 存储在高延迟旋转磁盘上时对 commit_delay 进行微调特别有用,但即使在同步时间非常快的存储介质(例如固态硬盘或带有电池供电写入高速缓存的 RAID 阵列)上,其收益也可能很大;但绝对应该针对代表性工作负载对此进行测试。在这些情况下,应使用更高值 commit_siblings ,而 commit_siblings 值较小通常有助于提高延迟介质的性能。请注意,将 commit_delay 设置的过高很可能极大地增加事务延迟,从而导致事务总吞吐量受到影响。

commit_delay 设置为零(默认值)时,仍可能会出现某种形式的群组提交,但是每个群组将只包含在先前的刷新操作(如果有)发生的窗口期间到达需要刷新其提交记录的点的会话。在客户端数量较高时,往往会出现“门道效应”,即使 commit_delay 为零,群组提交的效果也会变得显着,因此显式设置 commit_delay 往往帮助较小。只有在(1)有一些并发提交的事务,并且(2)吞吐量在一定程度上受提交速率限制时,设置 commit_delay 才有帮助;但在旋转延迟较高的情况下,此设置可以有效地增加只有两个客户端(即一个提交客户端和一个同级事务)的事务吞吐量。

wal_sync_method 参数确定 PostgreSQL 将如何要求内核强制将 WAL 更新写出到磁盘。除了 fsync_writethrough 外,在其他选项都没有强制刷新磁盘高速缓存的情况下 fsync_writethrough 可能会强制刷新磁盘高速缓存,因此所有这些选项在可靠性方面应该相同。但是,速度最快的选项取决于具体平台。你可以使用 pg_test_fsync 程序测试不同选项的速度。请注意,如果 fsync 已关闭,则此参数不相关。

启用 wal_debug 配置参数(前提是 PostgreSQL 已编译出对它的支持)将导致向服务器日志中记录每个 XLogInsertRecordXLogFlush WAL 调用。此选项将来可能会被更通用的机制所取代。

有两个内部函数用于将 WAL 数据写入磁盘:XLogWriteissue_xlog_fsync。在启用 track_wal_io_timing 时,XLogWrite 写入 WAL 数据的时间总数和 issue_xlog_fsync 将 WAL 数据同步到磁盘的时间总数分别在 pg_stat_wal 中被计为 wal_write_timewal_sync_time。当 WAL 缓冲区中没有空间容纳新记录时,XLogWrite 一般由 XLogInsertRecord(),XLogFlush 和 WAL 写入器调用,以便将 WAL 缓冲区写入磁盘并调用 issue_xlog_fsync。在 XLogWrite 中,issue_xlog_fsync 通常被用来将 WAL 文件同步到磁盘。如果 wal_sync_method 要么是 open_datasync,要么是 open_sync,则 XLogWrite 中的写操作保证将写入的 WAL 数据同步到磁盘,而 issue_xlog_fsync 不执行任何操作。如果 wal_sync_method 要么是 fdatasyncfsync,要么是 fsync_writethrough,该写操作将 WAL 缓冲区移至内核缓存,而 issue_xlog_fsync 将它们同步到磁盘。不管 track_wal_io_timing 的设置如何,XLogWrite 将 WAL 数据写入和 issue_xlog_fsync 将 WAL 数据同步到磁盘的次数分别在 pg_stat_wal 中被计为 wal_writewal_sync

recovery_prefetch 参数可用于通过指示内核启动读取磁盘块(它们很快就会被需要,但当前不在 PostgreSQL 的缓冲池中)的方式缩短恢复过程中的 I/O 等待时间。 maintenance_io_concurrencywal_decode_buffer_size 设置分别限制预取并发性和预取距离。默认情况下,它被设置为 try,并在 posix_fadvise 可用的系统上启用此功能。