有几个与 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_size
、max_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
中,直到解决此情况。使用复制槽的缓慢或失败的备用服务器将产生相同的效果(请参阅 第 27.2.6 节)。
在归档恢复或备用模式下,服务器会定期执行重启点,这类似于正常操作中的检查点:服务器强制其所有状态写入磁盘,更新 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
中获取锁后休眠多少微秒,而组提交跟随者在领导者后面排队。此延迟允许其他服务器进程将它们的提交记录添加到 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 已编译并支持它)将导致每个 XLogInsertRecord
和 XLogFlush
WAL 调用被记录到服务器日志中。此选项将来可能会被更通用的机制取代。
有两个内部函数将 WAL 数据写入磁盘:XLogWrite
和 issue_xlog_fsync
。当 track_wal_io_timing 被启用时,XLogWrite
写入和 issue_xlog_fsync
将 WAL 数据同步到磁盘的总时间分别被计为 pg_stat_wal 中的 wal_write_time
和 wal_sync_time
。通常由 XLogInsertRecord
(当 WAL 缓冲区中没有新记录的空间时)、XLogFlush
和 WAL 写入器调用 XLogWrite
,将 WAL 缓冲区写入磁盘并调用 issue_xlog_fsync
。通常由 XLogWrite
调用 issue_xlog_fsync
将 WAL 文件同步到磁盘。如果 wal_sync_method
是 open_datasync
或 open_sync
,则 XLogWrite
中的写操作保证将写入的 WAL 数据同步到磁盘,并且 issue_xlog_fsync
不执行任何操作。如果 wal_sync_method
是 fdatasync
、fsync
或 fsync_writethrough
,则写操作将 WAL 缓冲区移动到内核缓存,并且 issue_xlog_fsync
将它们同步到磁盘。无论 track_wal_io_timing
的设置如何,XLogWrite
写入和 issue_xlog_fsync
将 WAL 数据同步到磁盘的次数也分别计为 pg_stat_wal
中的 wal_write
和 wal_sync
。
可以使用 recovery_prefetch 参数通过指示内核启动对磁盘块的读取(这些磁盘块很快将需要,但当前不在 PostgreSQL 的缓冲池中)来减少恢复期间的 I/O 等待时间。 maintenance_io_concurrency 和 wal_decode_buffer_size 设置分别限制预取并发性和距离。默认情况下,它设置为 try
,这会在 posix_fadvise
可用的系统上启用该功能。