有几个WAL与 WAL 相关的配置参数会影响数据库性能。本节解释它们的使用。有关设置服务器配置参数的常规信息,请参阅第 19 章。
检查点是事务序列中的点,在这些点上保证堆和索引数据文件已使用该检查点之前写入的所有信息进行了更新。在检查点时间,所有脏数据页都会被刷新到磁盘,并且一个特殊的检查点记录会被写入 WAL 文件。(更改记录之前会被刷新到WAL文件中。)在发生崩溃时,崩溃恢复过程会查看最新的检查点记录,以确定 WAL 中的哪个点(称为重做记录),它应该从该点开始 REDO 操作。在该点之前对数据文件所做的任何更改都保证已在磁盘上。因此,在检查点之后,包含重做记录的 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 允许您强制检查点写入的 OS 页面在可配置的字节数后刷新到磁盘。否则,这些页面可能会保留在操作系统的页面缓存中,从而在检查点结束时发出 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
中,直到问题得到解决。使用复制槽的慢速或失败的备用服务器将具有相同的效果(请参阅第 26.2.6 节)。
在归档恢复或备用模式下,服务器会定期执行重启点,这与正常操作中的检查点类似:服务器强制将其所有状态写入磁盘,更新pg_control
文件以指示已处理的 WAL 数据不需要再次扫描,然后回收pg_wal
目录中的任何旧 WAL 段文件。重启点的执行频率不能高于主服务器上的检查点,因为重启点只能在检查点记录处执行。重启点可以由计划或外部请求触发。pg_stat_checkpointer
视图中的restartpoints_timed
计数器计算第一种情况的数量,而restartpoints_req
计数器计算第二种情况的数量。当自上次执行重启点以来至少经过了checkpoint_timeout秒,或者上次尝试执行重启点失败时,会在到达检查点记录时按计划触发重启点。在后一种情况下,下一个重启点将在 15 秒后安排。如果由于类似于检查点的原因,特别是当 WAL 大小即将超过max_wal_size时,会按请求触发重启点。但是,由于执行重启点的限制,恢复期间通常会超过max_wal_size
,最多可达一个检查点周期的 WAL。(无论如何,max_wal_size
永远不是硬性限制,因此您应该始终留出足够的空间以避免磁盘空间不足。)pg_stat_checkpointer
视图中的restartpoints_done
计数器计算已实际执行的重启点数量。
在某些情况下,当主服务器上的 WAL 大小快速增加时,例如在大量 INSERT 操作期间,备用服务器上的restartpoints_req
计数器可能会出现峰值增长。发生这种情况是因为由于 XLOG 消耗增加而创建新重启点的请求无法执行,因为自上次重启点以来,安全检查点记录尚未在备用服务器上重放。此行为是正常的,不会导致系统资源消耗增加。在与重启点相关的计数器中,只有restartpoints_done
计数器表示已花费了明显的系统资源。
有两个常用的内部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 个其他会话,则不会发生休眠;这样可以避免在不太可能有其他会话很快提交时休眠。请注意,在某些平台上,休眠请求的分辨率为 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
设置过高可能会使事务延迟增加到 total 事务吞吐量受到影响的程度。
当 commit_delay
设置为零(默认值)时,仍然可能发生一种形式的组提交,但每个组将仅由那些在上次刷新操作(如果有)正在进行时达到需要刷新其提交记录的会话组成。在较高的客户端计数下,往往会出现“通道效应”,因此即使 commit_delay
为零,组提交的效果也会变得显著,因此显式设置 commit_delay
的帮助往往较少。只有当 (1) 存在一些并发提交的事务,并且 (2) 吞吐量在某种程度上受到提交速率的限制时,设置 commit_delay
才会有所帮助;但对于高旋转延迟,此设置可以有效地在至少两个客户端(即,一个具有一个兄弟事务的提交客户端)的情况下提高事务吞吐量。
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
计数。XLogWrite
通常由 XLogInsertRecord
(当 WAL 缓冲区中没有新记录的空间时)、XLogFlush
和 WAL 写入器调用,以将 WAL 缓冲区写入磁盘并调用 issue_xlog_fsync
。issue_xlog_fsync
通常由 XLogWrite
调用以将 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
可用的系统上启用该功能。
如果您在文档中发现任何不正确、与您使用特定功能的体验不符,或需要进一步澄清的地方,请使用此表单报告文档问题。