29.4. WAL配置

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

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

按检查点要求,刷新所有垃圾数据页面到磁盘会导致显著的I/O负载,因此, 检查点进行了限制,在下一个检查点开始时,I/O在检查点开始结束时开启。 这会在检查点进行时降低性能损耗。

服务器的后端写进程每checkpoint_segments个日志段或每 checkpoint_timeout秒就创建一个检查点,以先到为准。 缺省设置分别是3个段和300秒(5分钟)。我们也可以用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_segmentsWAL段的分数已使用完, 或已经过了给定的checkpoint_timeout秒数时停止检查点。 缺省值是0.5,PostgreSQL可以在下次检查点开始之前用大约一半的时间完成检查点。 在一个正常操作时就很接近最大I/O吞吐量的操作系统上,可以增加checkpoint_completion_target以降低 检查点时的I/O负载。这样做的弊端在于会延长检查点,从而影响恢复时间,因为会保留更多的在恢复中可能用到的WAL段。 尽管checkpoint_completion_target最大可以设置为1.0,最好不要设置那么大,最大0.9,因为检查点期间的操作不仅仅 包括写脏缓冲区。设置为1.0极有可能会导致不会按时完成检查点,从而由于所需的WAL段的数目的意外变化造成性能丢失。

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

在归档或待机模式时,服务器在正常操作中会定期执行类似于检查点的 restartpoints: 服务器会强制将他的状态写入磁盘,更新pg_control文件来表明 预处理WAL数据不需要再次扫描,然后便会回收在pg_xlog目录下所有旧日志文件。 一个重启点的触发在上一个重启点至少一个检查点记录已经重新运行并且checkpoint_timeout通过时。 在标准模式下,如果一个checkpoint_segments日志声明,重启点的触发同样在上一个重启点 至少一个检查点记录已经重新运行并且checkpoint_timeout通过时。 重启点在主机不会比检查点执行的更多一些,因为重启点仅能够在检查点记录上执行。

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

commit_delay定义了后端在使用LogInsert 向日志中写了一条已提交的记录之后,再执行一次LogFlush 之前休眠的毫秒数。这样的延迟可以允许其它的后端把它们提交的记录追加到日志中, 这样就可以用一次日志同步把所有日志刷新到日志中。如果没有打开fsync 或者当前少于commit_siblings个处于活跃事务状态的其它后端时则不会发生休眠; 这样就避免了在其它事务不会很快提交的情况下睡眠。请注意,在大多数平台上, 休眠要求的分辩率是 10 毫秒,所以任何介于 1 和 10000 微秒之间的非零commit_delay 的作用都是一样的。适用这些参数的比较好的数值还不太清楚;我们鼓励你多做试验。

wal_sync_method参数决定PostgreSQL 如何请求操作系统内核强制将WAL更新输出到磁盘。 只要满足可靠性,那么所有选项应该都是一样的,除 fsync_writethrough,可有时 强制刷新磁盘高速缓存,即使其他选项时,不这样做。 然而,这是很具体的平台,其中之一将是最快的; 在PostgreSQL源代码树,你可以测试选项的速度,使用的实用src/tools/fsync。 请注意如果你关闭了fsync的话这个参数就无关紧要了。

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