当服务器正常运行时,WAL文件不断被写入磁盘。但是,这些写操作是顺序的:几乎没有随机访问,因此即使是HDD也可以处理这个任务。由于这种类型的加载与典型的数据文件访问非常不同,因此有必要为WAL文件设置一个单独的物理存储,并用一个指向已挂载文件系统中的目录的符号链接替换PGDATA/pg_wal编录。
有几种情况下,必须同时写入和读取WAL文件。第一个是明显的崩溃恢复案例;第二个是流复制。walsender进程直接从文件中读取WAL条目。因此,如果副本没有接收到WAL条目,而所需的页面仍在主服务器的操作系统缓冲区中,则必须从磁盘读取数据。但是访问仍然是顺序的,而不是随机的。
WAL条目可以用以下一种方式写入:
当前模式由synchronous_commit参数定义。
同步模式。 为了可靠地注册提交的事实,仅仅将WAL条目传递给操作系统是不够的;您必须确保磁盘同步已成功完成。由于同步意味着实际的I/O操作(相当慢),因此尽可能少地执行它是有益的。
为此,完成事务并将WAL条目写入磁盘的后端可以执行一个由commit_delay参数定义的小暂停。但是,只有当系统中至少有5个commit_sibling活动事务时才会发生这种情况:在此暂停期间,其中一些事务可能会完成,服务器将设法一次性同步所有WAL条目。这很像扶着电梯门让别人冲进来。
缺省情况下,没有暂停。只有对执行大量短OLTP事务的系统修改commit_delay参数才有意义。
在可能的暂停之后,完成事务的进程将所有累积的WAL条目刷新到磁盘并执行同步(保存提交条目和与此事务相关的所有先前条目非常重要;其余部分之所以被编写,只是因为它不会增加成本)。
从这个时候开始,ACID的持久性需求得到了保证——事务被认为是可靠地提交了。这就是为什么同步模式是默认的。
同步通信的缺点是较长的延迟(COMMIT命令在同步结束之前不会返回控制)和较低的系统吞吐量,特别是对于OLTP负载。
异步模式。 要启用异步提交,必须关闭synchronous_commit参数。在异步模式下,walwriter进程将WAL条目写入磁盘,该进程在工作和睡眠之间交替进行。暂停的持续时间由wal_writer_delay值定义。
从暂停中唤醒,进程检查缓存中是否有新的完全填满的WAL页面。如果出现任何这样的页面,进程将它们写到磁盘,跳过当前页面。否则,它将写入当前的半空页面,因为它已经唤醒了。
该算法的目的是避免多次刷新同一个页面,这为具有大量数据更改的工作负载带来了明显的性能提升。
虽然WAL缓存被用作环形缓冲区,但是walwriter在到达缓存的最后一页时停止;暂停后,下一个写作周期从第一页开始。因此,在最坏的情况下,walwriter需要运行三次才能到达特定的WAL条目:首先,它将写入位于缓存末尾的所有完整页面,然后它将返回到开头,最后,它将处理包含条目的未填充页面。但在大多数情况下,这需要一到两个周期。
每次写入wal_writer_flush_after数据量时执行同步,并在写入周期结束时再次执行同步。
异步提交比同步提交快,因为它们不需要等待物理写入磁盘。但是可靠性会受到影响:您可能会丢失在故障之前3×wal_writer_delay时间范围内提交的数据(默认情况下为0.6秒)。
在现实世界中,这两种模式相辅相成。在同步模式下,与长事务相关的WAL条目仍然可以异步写入空闲的WAL缓冲区。反之亦然,即使在异步模式下,与即将从缓冲区缓存中驱逐的页面相关的WAL条目也会立即刷新到磁盘中,否则无法继续操作。
在大多数情况下,系统设计师必须在性能和耐用性之间做出艰难的选择。
还可以为特定事务设置synchronous_commit参数。如果可以在应用程序级别将所有事务分类为绝对关键(例如处理财务数据)或不太重要,则可以提高性能,同时承担只丢失非关键事务的风险。
为了了解异步提交的潜在性能增益,让我们使用pgbench测试比较两种模式下的延迟和吞吐量。
首先,初始化所需的表:
在异步模式下,这个简单的基准测试显示出更低的延迟和更高的吞吐量(TPS)。当然,每个特定系统都有自己的数据,具体取决于当前负载,但很明显,对短OLTP事务的影响是非常明显的。
让我们恢复默认设置: