2.4 调整检查点和XLOG
目前为止,这一章已经提供深入洞察PostgreSQL如何写入数据,一般来说,XLOG是用来干什么的。考虑到这方面的知识,我们现在可以继续并学习我们能做些什么来使我们的数据库在复制和单台服务器运行的两种情况更加有效的工作。
2.4.1 理解检查点
在本章中,我们已经看到在数据可能到其它地方之前,它已经被写入到了XLOG。问题是,如果XLOG从未被删除,显然,在没有填满磁盘的同一时间,我们不会永远写到XLOG中。
要解决这个问题,XLOG必须在某一时刻被删除。这个过程就是所谓的检查点。
从这个问题所带来的主要问题是:什么时候XLOG可以被截断到某一特定的点?答案是:PostgreSQL把XLOG中所有的东西都放到存储文件中。如果XLOG中的所有更改也被放到数据文件中,XLOG才可以被截断。
[请记住,只写数据是没有价值的,我们还必须把数据刷新到数据表中。]
在某种程度上,XLOG可以被看作在有情况发生时,数据文件修理工。如果一切都被完全修复,修理指令就可以被放心地删除了。这就是在一个检查点期间所发生的。
2.4.2 配置检查点
对一致性来说,检查点是非常好重要的,但是它们对性能来说也是非常重要的。如果检查点配置不当,您可能会面临严重的性能下降。
当谈到配置检查点,下面是一些相关参数。请注意,所有这些参数可以在postgresql.conf 中进行更改:
checkpoint_segments = 3
checkpoint_timeout = 5min
checkpoint_completion_target = 0.5
checkpoint_warning = 30s
在下面的章节中,我们将看看这些变量:
关于段和超时
checkpoint_segments 和 checkpoint_timeout 将定义两个检查点之间的距离。检查点发生或者当我们用完段或者实际到了。
请记住,一个段通常是16MB,所以三个段意味着我们将每48MB做一个检查点。在现代硬件上,16MB是远远不够的。在一个典型的生产系统中,一个检查点256个间隔或者更高是完全可行的。
但是,当设置checkpoint_segments的时候,有一件事必须记在您的脑后:在崩溃的情况,PostgreSQL必须重放自从最后一个检查点的所有的更改。如果两个检查点之间的距离非常大,您可能会注意到,您的故障数据库实例需要很长的时间才能再次启动。为了可用性,这种情况应该避免。
[在崩溃之后,总会有性能与恢复时间之间的权衡;您必须平衡您相应的配置。]
checkpoint_timeout 也非常重要。它是两个检查点之间所允许的时间上线。不更改时间而无限制地增加checkpoint_segments是没有意义的。在大型系统中,对许多人来说,增加checkpoint_timeout已被证明是有意义的。
[在PostgreSQL中,您会发现,有一个事务日志的常数。不同于在其它数据库系统中,XLOG文件的数目和事务的最大尺寸没有关系;一个事务的大小可以很容易地超过两个检查点之间的距离。]
写或不写?
我们在本章已经知道,在COMMIT时,我们不能确定数据是否已经在数据文件。
因此,如果数据文件不必是一致的,为什么不改变数据写入的时间点?这正是我们使用checkpoint_completion_target可以做到的。这个思想是有一个指定检查点完成的目标,作为两个检查点之间的总时间的一部分。
现在让 我们讨论三个场景来说明checkpoint_completion_target的目的:
场景1-存储股市数据
在这个场景中,我们将存储道琼斯工业(DJIA)所有股票的最近行情。我们不想存储所有股票价格的历史,而是最近的,当前的价格。
考虑到我们正在处理的数据的类型,我们可以假设我们有一个由UPDATE语句决定的工作负载。
会发生什么?PostgreSQL必须一遍又一遍地更新相同的数据。鉴于一个事实,DJIA由30个不同的股票组成,数据量是非常有限的,并且我们的表也非常小。除此之外,价格可能每秒更新一次,或者更频繁。
在内部,情况是这样的:当第一个UPDATE到来时,PostgreSQL将获得一个块,放入内存并修改它。每一个后续的UPDATE将最有可能改变相同的块。从逻辑上讲,所有的写操作必须写到事务日志,但是在共享缓冲区中的缓存块发生了什么?一般的规则是:如果有许多UPDATE(分别对相同的块做更改),尽可能地把块保存在内存中是明智的;这将通过一个写多个变化更改来极大地增加避免I/O的可能性。
[如果你要增加在一个磁盘I/O有许多更改的肯能性,考虑降低checkpoint_complection_target。块将在内存中停留更长的时间,因此在写发生之前,很多变化可能进入相同的块。.]
该方案只是介绍,checkpoint_completion_target 为 0.05(或者5%)可能是合理的。
场景2-批量装载
在我们的第二个场景中,我们将加载1TB的数据到一个空表。如果你正在一次加载这么多的数据,再次命中你十分钟之前命中的数据块的可能性是多少?这个可能性基本上是0,在这种情况下,缓冲区没有一点写入,因为我们会很容易通过空闲以及等待I/O的发生而错过磁盘的容量。
在批量加载期间,我们要使用我们一直都有的所有的I/O能力。为了确保PostgreSQL立即把数据写出来,我们必须把checkpoint_completion_target的值增加到接近1。
场景3-I/O峰值和吞吐量方面的考虑
尖锐的剑锋可以杀死你,至少它们可以造成应该避免的严重的伤害。在你周围的真实世界中真实的东西在数据库世界中也是真实。
在这个场景中,我们要假设一个应用程序存储一个电话公司所谓的呼叫详细记录(CDRs)。你可以想象,很多写将会发生,并且人们一整天都在打电话。当然,会有人们在打电话时,立即有另外一个电话跟在后面,但我们也将见证好多人在一周之内只打一次电话。
从技术上将,这意味着有一个好机会,在共享内存中的块,它最近已经被更改了,将面临第二次或者第三次的更改,但是,我们也将对那些不再被访问的块做出巨大的更改。
这种情况我们该如何处理呢?晚点写出数据,以便尽可能多的更改将在之前已经修改过的页面上进行更改。但是,在检查点期间会发生什么呢?如果更改(在这种情况下,脏页)囤积了太长时间,检查点本身将会很激烈,很多块必须在很短的时间内被写入。这会导致所谓的I/O剑锋。在一个I/O剑锋,你会看到你的系统是繁忙的。这可能显示缺少响应时间,缺少响应时间可以被你的终端用户感受到。
这给问题增加了一个维度:可预测的响应时间。
让我们这样说吧:让我们假设你已经成功地使用了一段时间的网上银行。你很高兴。现在,一些人在你的网上银行找到了一个调整方法,这使得网上银行背后的数据库快了50%,但是,这增加了一个缺点:每天有两个小时,该系统不可达。显然,从性能的角度来说,吞吐量会比较好:
24 hours * 1 X < 22 hours * 1.5 X
但是,你的客户会开心吗?很显然,你不会。这是一个典型的用户案例,优化最大吞吐量并没有好处。如果你可以满足你性能需求,有均匀的响应时间而有一点小小的性能损失作为代价可能是明智的。在我们的银行的例子中,这将意味着你的系统是24×7运行而不是每天只运行22小时。
[如果你的互联网带宽比以前快10倍,你会频繁地支付你负担吗?显然,你不会。有时,它不是关于每秒多少事务的优化,而是你以最合理的方式处理一个预先定义的负载量的优化方法。]
同样的概念也适用于我们的电话应用程序。我们在检查点期间写所有的更改,因为这可能导致检查点期间的延迟问题。这对立即更改数据文件也没有好处(意思是:高的checkpoint_completion_target),因为我们会写得太多,太频繁。
这是一个你必须妥协的典型的例子,checkpoint_completion_target为0.5 可能在这种情况下是最好的注意。
结论
从三个例子中得出的结论是,没有适合所有用途的配置。为了得出一个好的可行的配置,你真的需要考虑一下你在处理的数据类型。对于许多应用,0.5的值已经证明刚刚好。
2.4.3 调整WAL缓冲区
在本章中,我们已经调整了一些重要的参数,例如,shared_buffers, fsync,等等。还有一个参数,但是,这可能会对性能产生重大的影响。参数 wal_buffers 被设计用来告诉PostgreSQL,用多少内存来记录目前还没有被写入磁盘的XLOG。所以,如果有人在注入大事务时,在COMMIT之前,PostgreSQL将不会把任何变化较小的表写到XLOG。请记住,在崩溃期间,如果一个未提交的事务丢失了,我们不需要在意它,因为COMMIT是在日常生活中唯一重要的事情。在COMMIT发生之前,这对使用较大的块写XLOG很有意义。这正是wal_buffers做的:除非在postgresql.conf中手动更改,它是一个自动调整的参数(用-1表示),在XLOG写回磁盘之前,它使得PostgreSQL花费3%的shared_buffers,但不超过16MB来保持XLOG。
[在老版本的PostgreSQL中,这个参数是64KB。对现代的机器来说 这么低的值是不合理的。如果你在运行一个老版本的PostgreSQL,可以考虑把wal_buffers增加到16MB。对合理大小的数据库实例来说,这通常是一个合理的值。]