有许多WAL- 相关配置参数,它们会影响数据库性能。本节说明如何使用它们。有关设置服务器配置参数的一般信息,请参阅 第 19 章。
检查点 是事务序列中的点,在这些点上可以保证堆和索引数据文件已使用检查点之前写入的所有信息进行更新。在检查点时间,所有脏数据页面都会刷新到磁盘,并且一个特殊的检查点记录会写入到 WAL 文件。(更改记录以前已刷新到WAL文件。)如果发生崩溃,崩溃恢复程序会查看最新的检查点记录以确定 WAL(称为重做记录)中它应从中启动重做操作的点。在该点之前对数据文件所做的任何更改都保证已在磁盘上。因此,在检查点之后,不再需要包含重做记录的检查点之前的 WAL 段,并且可以回收或删除这些段。(当WAL归档正在执行,则在回收或移除前必须归档 WAL 段。)
将所有脏数据页刷新到磁盘的检查点要求会导致明显的 I/O 负载。因此,会对检查点活动进行节流,以便在检查点开始时开始 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
传输之类的批量操作可能会导致出现这样的警告。
为避免对输入/输出系统进行大量的页面写入,在检查点期间写入脏缓冲区的时间将会延长。该时间段由checkpoint_completion_target 控制,它作为检查点间隔的一部分(通过使用checkpoint_timeout
进行配置)。输入/输出速率进行了调整,以便检查点在checkpoint_timeout
秒数中给出的部分时间过去时或在超过max_wal_size
之前完成,时间以较早者为准。对于默认值 0.9,预计PostgreSQL会在下一个计划检查点之前(大约为上次检查点持续时间的 90%)完成每个检查点。这尽可能地扩大了输入/输出,以便在整个检查点间隔内检查点输入/输出负载保持一致。其缺点是延长检查点会影响恢复时间,因为需要保留更多的 WAL 线程段以备用于可能的恢复中。对于恢复所需时间量感到担忧的用户可能会希望减少checkpoint_timeout
,以便更频繁地进行检查点,但仍可以将输入/输出跨检查点间隔进行分布。或者,可以减少checkpoint_completion_target
,但这将导致输入/输出更加密集的时间段(在检查点期间)和输入/输出较少的时间段(在完成检查点之后但在下一个计划检查点之前),因此不推荐这样做。尽管可以将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
中累积,直至情况得到解决。使用复制槽的慢或失败的备用服务器也会产生相同的效果(请参阅第 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
计数器会统计实际已执行的重启点。
在某些情况下(例如进行大量 INSERT 操作时),主服务器上的 WAL 大小会快速增大,而备用服务器上的 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
中获取锁后组提交 leader 进程将在多长时间内休眠,与此同时组提交跟随者在 leader 后面排队。此延迟允许其他服务器进程将它们的提交记录添加到 WAL 缓冲区,以便它们全部被 leader 的最终同步操作刷新。如果未启用 fsync,或者当前有少于 commit_siblings 个其他会话处于活动事务中,则不会发生休眠;这避免了在任何其他会话不太可能很快提交的情况下休眠。请注意,在某些平台上,休眠请求的分辨率为十毫秒,因此任何介于 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
设置为零(默认值)时,仍然有可能出现某种形式的组提交,但每个组只会包含在以前刷新操作(如果有)发生期间达到需要刷新其提交记录的点的会话。 在较高的客户端数量下,往往会出现 “人行道效应”,因此即使 commit_delay
为零,组提交的效果也会变得显著,因此显式设置 commit_delay
往往帮助较小。只有在以下两种情况下,设置 commit_delay
才可能有所帮助:(1)存在一些并发提交的事务,并且(2)吞吐量在一定程度上受到提交速率的限制;但借助较高的旋转延迟,此设置可有效地提高事务吞吐量,客户端低至两个(即,一个提交客户端和一个同级事务)。
参数 wal_sync_method 决定了 PostgreSQL 将如何要求 kernel 强制WAL更新到磁盘。除 fsync_writethrough
外,所有选项在可靠性方面应该相同,即使其他选项未执行时,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 缓冲区移动到 kernel 缓存中,而 issue_xlog_fsync
则会将它们同步到磁盘。无论 track_wal_io_timing
的设置如何,XLogWrite
写入的次数和 issue_xlog_fsync
同步 WAL 数据到磁盘的次数也会分别计算为 pg_stat_wal
中的 wal_write
和 wal_sync
。
可以通过指示内核启动即将需要的但目前不在 PostgreSQL 缓冲池中的磁盘块的读取来使用 recovery_prefetch 参数来减少恢复过程中的 I/O 等待时间。 maintenance_io_concurrency 和 wal_decode_buffer_size 设置分别限制预取并发性和距离。默认情况下,它设置为 try
,在 posix_fadvise
可用的系统上启用该功能。