29.4. WAL配置

有几个与WAL相关的参数会影响数据库性能。本节讨论它们的使用。 参阅第 18 章获取有关服务器配置参数的一般信息。

检查点是事务序列中的点, 在该点之前的所有信息都确保已经写到数据文件中去了。在设置检查点时, 所有脏数据页都刷写到磁盘并且向日志文件中写入一条特殊的检查点记录。 (变更记录已经预先刷新到WAL文件了。)在发生崩溃的时候, 崩溃恢复过程查找最后的检查点记录,判断应该从日志中的哪个点(称为 redo 记录)开始 REDO 操作, 在该记录之前对数据文件的任何修改都保证已经写在磁盘上了。因此,在检查点之后, 任何在包含 redo 记录点之前的日志段都不再需要,因此可以循环使用或者删除。当然, 在进行WAL归档的时候,这些日志段在循环利用或者删除之前必须先归档。

检查点需要刷写所有脏数据页面到磁盘,这会导致显著的I/O负载,因此,对设置检查点的行为进行了限制, I/O在检查点开始时启动,在下一个检查点开始之前完成,这会在检查点进行时降低性能损耗。

服务器的检查点进程每checkpoint_segments个日志段或每 checkpoint_timeout秒就创建一个检查点,以先到为准。 缺省设置分别是 3 个段和 300 秒(5分钟)。如果自前一个检查点以来没有WAL写入, 那么将跳过新的检查点,即使已经超过了checkpoint_timeout。 (如果使用了WAL归档并且你希望放置一个低一些的限制在多久文件归档一次上,以限制潜在的数据丢失, 你应该调整archive_timeout参数而不是检查点参数。) 我们也可以用 SQL 命令CHECKPOINT强制创建一个检查点。

减少checkpoint_segments和/或checkpoint_timeout 会更频繁的创建检查点。这样就允许更快的崩溃后恢复(因为需要重做的工作更少)。不过, 我们必须在更快的恢复与更频繁的刷新脏数据页所带来的额外开销之间进行平衡。并且, 如果开启了full_page_writes(缺省开启),那么还有其它的因素需要考虑。 为了保证数据页的一致性,在每个检查点之后的第一次数据页变化会导致对整个页面内容的日志记录。 因此,减小检查点时间间隔会导致输出到 WAL 日志中的数据量增加,从而抵销一部分使用较短时间间隔的目标, 并且无论如何都会产生更多的磁盘 I/O。

检查点的开销相当高,首先是因为它需要写出所有当前脏缓冲区,其次是因为导致前面讨论过的额外后继 WAL 流量。 因此把检查点参数设置得足够高,让检查点发生的频率降低是明智的。可以通过设置 checkpoint_warning对检查点参数进行一个简单自检。如果检查点发生的间隔接近 checkpoint_warning秒,那么将向服务器日志输出一条消息,建议你增加checkpoint_segments 的数值。偶尔出现这样的警告并不会导致警报,但是如果出现得太频繁,那么就应该增加检查点控制参数。 如果你没有把checkpoint_segments设置得足够大,那么批量操作的时候 (比如大批的COPY传输)会导致出现大量此类警告消息。

为了避免大量的块写操作塞满I/O系统,在检查点期间写脏缓冲会持续一段时间。这个时间是由 checkpoint_completion_target控制的,作为检查点时间间隔的一小部分。 调整I/O速率以便当从检查点开始时给出的checkpoint_segments段的分数已使用完, 或已经过了给定的checkpoint_timeout秒数时完成检查点,不管时间哪个更早。 缺省值是0.5,PostgreSQL可以在下次检查点开始之前用大约一半的时间完成检查点。 在一个正常操作时就很接近最大I/O吞吐量的操作系统上,可以增加checkpoint_completion_target 以降低创建检查点时的I/O负载。这样做的弊端在于会延长检查点,从而影响恢复时间, 因为会保留更多的在恢复中可能用到的WAL段。尽管checkpoint_completion_target 最大可以设置为1.0,最好不要设置那么大,最大0.9,因为检查点期间的操作不仅仅包括写脏缓冲区。 设置为1.0极有可能会导致不会按时完成检查点,从而由于所需的WAL段的数目的意外变化造成性能丢失。

至少会有一个 WAL 段文件,而且通常不会超过(2 + checkpoint_completion_target) * checkpoint_segments + 1 或checkpoint_segments + wal_keep_segments + 1个文件。 每个段文件通常为 16MB(你可以在编译服务器的时候修改它)。你可以用这些信息来估计WAL需要的空间。 通常,如果一个旧日志段文件不再需要了,那么它将得到回收(重命名为顺序的新的可用段)。 如果由于短期的日志输出高峰导致了超过3 * checkpoint_segments + 1个段文件, 那么当系统再次回到这个限制之内的时候,不需要的段文件将被删除,而不是回收利用。

在归档恢复或待机模式时,服务器在正常操作中会定期执行类似于检查点的restartpoints :服务器会强制将他的状态写入磁盘,更新pg_control 文件来表明已经处理了的WAL数据不需要再次扫描,然后便会回收在pg_xlog目录下所有旧日志段文件。 重启点不能比检查点运行的更频繁,因为重启点只能在检查点记录上执行。当到达检查点记录时, 如果从最后一个重启点至少已经过去checkpoint_timeout秒,那么触发重启点。 在待机模式下,如果自从最后一个重启点已经至少checkpoint_segments个日志段重放, 也会触发重启点。

有两个常用的内部WAL函数XLogInsertXLogFlushXLogInsert用于向共享内存中的WAL缓冲区里添加一条新记录。 如果没有空间存放新记录,那么XLogInsert就不得不写出(向内核缓存里写) 一些填满了的WAL缓冲。我们可不想这样,因为XLogInsert 用于每次数据库低层修改(比如插入记录)时都要在受影响的数据页上持有一个排它锁, 所以该操作需要越快越好;更糟糕的是,写WAL缓冲可能还会强制创建新的日志段, 它花的时间甚至更多。通常,WAL缓冲区应该由一个XLogFlush 请求来写和刷新,在大部分时候它都是发生在事务提交的时候以确保事务记录被刷新到永久存储器上去了。 在那些日志输入量比较大的系统上,XLogFlush请求可能不够频繁, 这样就不能避免XLogInsert进行写操作。在这样的系统上, 我们应该修改wal_buffers参数的值来增加WAL缓冲区的数量。 如果设置了full_page_writes并且系统相当繁忙, 把wal_buffers设置得高一些将有助于在紧随每个检查点之后的时间里平滑响应时间。

commit_delay定义了在XLogFlush 内请求一个锁后一组提交领导者进程将要休眠的毫秒数,组提交后面跟着排队的领导者。 这样的延迟可以允许其它的服务器进程把它们提交的记录追加到WAL缓存中, 这样就可以通过领导者的最终sync操作把所有记录刷新。如果没有打开fsync 或者当前少于commit_siblings个处于活跃事务状态的其它会话时则不会发生休眠; 这样就避免了在其它事务不会很快提交的情况下睡眠。请注意,在一些平台上, 休眠要求的分辩率是 10 毫秒,所以任何介于 1 和 10000 微秒之间的非零commit_delay 设置的作用都是一样的。也要注意,在一些平台上,睡眠操作可能比参数要求的时间稍长一些。

因为commit_delay的目的是允许每个刷新操作的开销分摊给并发的提交事务 (可能是事务潜在开销),在设置可以明智的选择前量化开销是必须的。开销越高,commit_delay 在一定程度上提高事务吞吐量越有效。pg_test_fsync程序可以用来测量单次WAL刷新操作使用的平均毫秒数。 这个程序报告的平均时间的一半用来在单个8kB写操作之后刷新,这个时间通常是commit_delay 最有效的设置,所以当优化特定负载时,建议使用这个值作为起始点。当WAL日志存储在高时延旋转磁盘上时, 调整commit_delay是尤其有用的,即使存储媒体有非常快的sync时间也是有显著效益的, 比如有电池备用写缓存的固态硬盘或RAID阵列;但是这应该明确对代表工作负载进行测试。commit_siblings 的更高值应该在诸如此类的情况下使用,而更小值通常对高时延媒体有帮助。注意, 总事务吞吐量太大时,commit_delay的设置太高会增加事务时延是极有可能的。

commit_delay设置为0时(缺省),仍然可能发生组提交, 但是每组将只由到达点的会话组成,在这个点它们需要刷新在前一个刷新操作(如果有) 发生时它们的提交记录。更高的客户端计数"舷梯效应"往往会发生, 所以组提交的影响会是显著的,即使commit_delay为0, 并且因此明确的设置commit_delay往往没什么用处。设置commit_delay 只能帮助以下情况:(1)有一些并发提交事务,(2)吞吐量通过提交率限制到某种程度; 但是对于高旋转延迟,只有两个客户端时,这个设置对增加事务吞入量是有效的(也就是, 单次提交客户端有一个兄弟事务)。

wal_sync_method参数决定PostgreSQL 如何请求操作系统内核强制将WAL更新输出到磁盘。只要满足可靠性, 那么所有选项应该都是一样的,除fsync_writethrough,可以有时强制刷新磁盘高速缓存, 即使其他选项不这样做。但是哪个最快则可能和平台密切相关。你可以使用 pg_test_fsync测试不同选项的速度。请注意如果你关闭了fsync 的话这个参数就无关紧要了。

打开wal_debug配置参数(前提是编译PostgreSQL 的时候打开了这个支持)将导致每次XLogInsertXLogFlush WAL 调用都被记录到服务器日志。这个选项以后可能会被更通用的机制取代。