Skip to content

30.5.WAL配置#

有几个与WAL相关的配置参数会影响数据库性能。本节将说明其用法。有关设置服务器配置参数的一般信息,请参阅第 20 章

检查点是事务序列中的点,在该点上可以保证堆和索引数据文件已使用该检查点之前写入的所有信息进行更新。在检查点时间,所有脏数据页都将刷新到磁盘,并且一个特殊的检查点记录将被写入WAL文件。(更改记录先前已刷新到WAL文件。)在发生崩溃的情况下,崩溃恢复过程会查看最新的检查点记录,以确定应从中启动重做操作的 WAL 中的点(称为重做记录)。在该点之前对数据文件所做的任何更改都保证已在磁盘上。因此,在检查点之后,包含重做记录的检查点之前的 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。偶尔出现这样的消息并不值得警报,但如果经常出现,则应增加检查点控制参数。如果您没有将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_commandarchive_library重复失败,则旧 WAL 文件将累积在pg_wal中,直到解决此情况。使用复制槽的缓慢或失败的备用服务器将产生相同的效果(请参阅第 27.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_buffers参数来增加WAL缓冲区的数量。当设置full_page_writes且系统非常繁忙时,设置更高的wal_buffers将有助于在每个检查点之后的立即期间平滑响应时间。

commit_delay参数定义了组提交领导进程在XLogFlush中获取锁后休眠多少微秒,而组提交跟随者在领导者后面排队。此延迟允许其他服务器进程将它们的提交记录添加到 WAL 缓冲区,以便它们全部由领导者的最终同步操作刷新。如果未启用fsync,或者当前处于活动事务中的会话少于commit_siblings,则不会发生休眠;这避免了在不太可能很快提交任何其他会话时休眠。请注意,在某些平台上,休眠请求的分辨率为十毫秒,因此任何非零commit_delay设置在 1 到 10000 微秒之间都会产生相同的效果。另请注意,在某些平台上,休眠操作可能比参数请求的时间稍长。

由于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往往不太有帮助。设置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写入和issue_xlog_fsync将 WAL 数据同步到磁盘的总时间分别被计为pg_stat_wal中的wal_write_timewal_sync_time。通常由XLogInsertRecord(当 WAL 缓冲区中没有新记录的空间时)、XLogFlush和 WAL 写入器调用XLogWrite,将 WAL 缓冲区写入磁盘并调用issue_xlog_fsync。通常由XLogWrite调用issue_xlog_fsync将 WAL 文件同步到磁盘。如果wal_sync_methodopen_datasyncopen_sync,则XLogWrite中的写操作保证将写入的 WAL 数据同步到磁盘,并且issue_xlog_fsync不执行任何操作。如果wal_sync_methodfdatasyncfsyncfsync_writethrough,则写操作将 WAL 缓冲区移动到内核缓存,并且issue_xlog_fsync将它们同步到磁盘。无论track_wal_io_timing的设置如何,XLogWrite写入和issue_xlog_fsync将 WAL 数据同步到磁盘的次数也分别计为pg_stat_wal中的wal_writewal_sync

可以使用recovery_prefetch参数通过指示内核启动对磁盘块的读取(这些磁盘块很快将需要,但当前不在PostgreSQL的缓冲池中)来减少恢复期间的 I/O 等待时间。maintenance_io_concurrencywal_decode_buffer_size设置分别限制预取并发性和距离。默认情况下,它设置为try,这会在posix_fadvise可用的系统上启用该功能。