ddia/pg/pg-tune-checkpoint.md
2018-02-08 14:07:06 +08:00

151 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Checkpoints Tuning Basics [译]
原文地址https://blog.2ndquadrant.com/basics-of-tuning-checkpoints/
在进行不重复的写操作的系统上,调优检查点对于获得良好性能至关重要。然而,检查点是我们经常在社区邮件列表和我们客户的性能调整评估过程中发现混淆和配置问题的领域之一。 另外一个是autovacuum前几天由Citus的Joe Nelson讨论过。那么让我来引导你通过检查点 - 他们做什么以及如何在PostgreSQL中调整它们。
## 什么是检查点?
PostgreSQL是依赖于预写日志WAL的数据库之一 - 所有的改变都会先写入一个日志一系列更改然后再写入数据文件。这提供了持久性因为在发生崩溃的情况下数据库可能会使用WAL来执行恢复 - 从WAL读取更改并将其重新应用于数据文件。
虽然这可能会使写入量增加一倍但实际上可能会提高性能。用户只需等待WAL刷新到磁盘而数据文件只在内存中修改然后在后台刷新。这很好因为虽然WAL写入本质上是连续的但写入数据文件通常是随机的。
假设系统崩溃数据库需要执行恢复。最简单的方法是从头开始从头开始重放整个WAL。最后我们应该得到一个完整和正确的数据库。缺点当然是需要保留和重放整个WAL。我们经常处理的数据库不是非常大比如几百GB但是每天产生几个TB的WAL。所以想象在运行一年的数据库上需要多少磁盘空间来保持所有的WAL以及在恢复过程中需要多少时间来重放。
但是如果数据库保证在给定的WAL位置wal_lsn处的所有数据变更都已经落盘。那么就可以在恢复期间确定这个位置并仅重播WAL的剩余部分从而显着减少恢复时间。而且它也可以移除“已知的安全点”之前的WAL。
这正是检查点的目的 - 确保WAL在某个时间点之前不再需要进行恢复从而减少磁盘空间要求和恢复时间。
*注意:如果你碰巧是一个玩家,你可能对检查点的概念很熟悉 - 你的角色通过了游戏中的某个点如果你没有击败下一个老板或掉入一个湖泊热熔岩你从最后一点而不是从一开始就开始。让我们看看如何在PostgreSQL中实现这一点;-)*
还有另外一个极端 非常频繁的检查点例如每隔一秒钟。这样可以只保留微量的WAL恢复极快不得不重播只有微小的WAL量。但它也会从异步写入数据页面退化为同步写入严重影响用户例如增加COMMIT延迟减少吞吐量
所以在实践中通常希望检查点不要频繁到影响用户,但又足够频繁以限制恢复和磁盘空间需求的时间。
## 触发检查点
有三到四个原因可以触发检查点:
* 直接执行CHECKPOINT命令
* 执行需要检查点的命令例如pg_start_backupCREATE DATABASE或pg_ctl stop | restart等
* 达到自上次检查点以来的配置时间量
* 从上一个检查点又名“用完WAL”或“填充WAL”生成配置的WAL量
前两点在这里相当不重要 - 那些是罕见的,手动触发的事件。这篇博文是关于如何配置其他两个事件,影响常规定期检查点。
这些时间/大小限制是使用两个配置选项设置的:
* `checkpoint_timeout = 5min`
* `max_wal_size = 1GB`在PostgreSQL 9.5之前是`checkpoint_segments`
使用这些默认PostgreSQL将每5分钟触发一次CHECKPOINT或者在WAL增长到磁盘上大约1GB之后。
注意max_wal_size是总WAL大小的软限制这有两个结果。首先数据库将尽量不超过它但是被允许因此在分区上保留足够的空闲空间并监视它。其次这不是一个“每个检查点”的限制 - 由于扩展检查点稍后解释WAL配额被分为2-3个检查点。因此根据checkpoint_completion_target使用max_wal_size数据库将在写入300-500 MB的WAL之后启动CHECKPOINT。
默认值相当低就像示例配置文件中的大多数其他默认值一样即使在像Raspberry Pi这样的小型系统上也可以工作。
但是,如何确定您的系统的良好价值?我们的目标不是过分频繁地或不经常地检查点,而我们的调整“最佳实践”包括两个步骤:
* 选择一个“合理”的`checkpoint_timeout`值
* 设置`max_wal_size`足够高,很少到达
很难说checkpoint_timeout的“合理”值是多少因为它取决于恢复时间目标RTO即什么是可接受的最大恢复持续时间。
但是这有点棘手因为checkpoint_timeout是生成WAL需要多长时间的限制而不是直接在恢复时间上。而遗憾的是无法确切说明需要多长时间才能恢复。 WAL通常由多个进程运行DML生成而恢复则由单个进程执行这种限制大多是固有的不可能很快消失。这不仅会影响本地恢复还会影响到流式复制到备用数据库。当然本地恢复通常在重启后立即发生当文件系统缓存很冷时。
但一般来说默认值5分钟是相当低的30分钟到1小时之间的值是相当普遍的。 PostgreSQL 9.6甚至将最大值增加到1天所以有些黑客认为这是一个好的主意。由于整页写入低值也可能导致写入放大我不打算在这里讨论
假设我们已经决定使用30分钟。
```
checkpoint_timeout = 30min
```
现在我们需要估计数据库在30分钟内产生多少WAL以便我们可以使用它来获得max_wal_size。 有几种方法可以确定生成多少WAL
* 使用`pg_current_xlog_insert_location()` 10之后是`pg_current_wal_insert_lsn`查看实际的WAL位置基本上在文件中偏移并计算每30分钟测量的位置之间的差异。
* 启用`log_checkpoints = on`然后从服务器日志中提取信息每个完成的检查点都会有详细的统计信息包括WAL的数量
* 使用来自pg_stat_bgwriter的数据其中也包含关于检查点数量的信息可以结合当前max_wal_size值的知识
例如,我们使用第一种方法。 在运行pgbench的测试机上我看到
```bash
postgres=# SELECT pg_current_xlog_insert_location();
pg_current_xlog_insert_location
---------------------------------
3D/B4020A58
(1 row)
... after 5 minutes ...
postgres=# SELECT pg_current_xlog_insert_location();
pg_current_xlog_insert_location
---------------------------------
3E/2203E0F8
(1 row)
postgres=# SELECT pg_xlog_location_diff('3E/2203E0F8', '3D/B4020A58');
pg_xlog_location_diff
-----------------------
1845614240
(1 row)
```
这表明在5分钟内数据库生成了〜1.8GB的WAL因此`checkpoint_timeout = 30min`将会是约10GB的WAL。不过如前所述max_wal_size是2 - 3个检查点的配额所以max_wal_size = 30GB3 x 10GB似乎是正确的。
其他方法使用不同的数据来源,但想法是相同的。
## 传播检查点
我建议你需要调整`checkpoint_timeout`和`max_wal_size`,但我并没有说出全部的真相。还有另一个参数叫做`checkpoint_completion_target`。但要调整它,你需要了解“传播检查点”是什么意思。
但在CHECKPOINT期间数据库需要执行以下三个基本步骤
* 识别共享缓冲区中的所有脏(已修改)块
* 将所有这些缓冲区写入磁盘(或者写入文件系统缓存)
* `fsync`将所有修改后的文件保存到磁盘
只有当所有这些步骤完成后检查点才算完成。您可以尽可能快地完成这些步骤即一次性写入所有脏缓冲区然后在文件上调用fsync实际上这就是直到PostgreSQL 8.2一直做的事情。但是由于填充文件系统缓存、喂饱设备会导致I/O延迟并影响用户会话。
为了解决这个问题PostgreSQL 8.3引入了“扩展检查点”的概念而不是一次写入所有的数据写入的时间很长。这使得操作系统有时间在后台刷新脏数据使得最终的fsync开销更小。
这些写入是基于进入下一个检查点的进度而被限制的, 数据库知道下一个检查点还剩多少时间/WAL空间并计算出应当写出多少个缓冲区。然而数据库一直拖到最后时刻才写入。这意味着最后一批写入仍然在文件系统缓存中使得最终的fsync调用在开始下一个检查点之前发出再次开销巨大。
所以数据库需要留出足够的时间,以便脏数据在后台刷新到磁盘 - 而页缓存Linux文件系统缓存的到期通常是由时间驱动的特别是通过这个内核参数
`vm.dirty_expire_centisecs = 3000`
这表示数据在30秒后过期默认情况下
注意:当涉及到内核参数时,调整`vm.dirty_background_bytes`很重要。在具有大量内存的系统上,默认值太高,从而使内核在文件系统缓存中积累了大量脏数据。内核通常决定一次刷新它们,减少扩展检查点的好处。
现在回到`checkpoint_completion_target = 0.5`。这个配置参数表示如果所有的写操作都完成的话到下一个检查点有多远。例如假设检查点仅由checkpoint_timeout = 5min触发数据库将会限制写入操作以便在2.5分钟后完成最后一次写入操作。然后操作系统又有2.5分钟将数据刷新到磁盘这样5分钟后发出的fsync呼叫廉价又快捷。
当然离开系统2.5分钟可能看起来过多考虑到到期超时只有30秒。您可能会增加`checkpoint_completion_target`例如到0.85这将使系统大约45秒比它需要的30秒多一点。这并不是推荐的因为在密集写入的情况下检查点可能比5分钟之后更早地被max_wal_size触发使操作系统少于30秒。
但是处理写入密集型工作负载的系统不太可能运行更高的checkpoint_timeouts值使默认的completion_target值肯定太低。例如如果将超时设置为30分钟则会强制数据库在前15分钟内完成所有写入以写入速率的两倍然后闲置15分钟。
相反您可以尝试使用此公式粗略设置checkpoint_completion_target
`checkpoint_timeout - 2min/ checkpoint_timeout`
其中30分钟约为0.93。有时候建议不要超过0.9--这可能是好的,你不可能观察到这两个值之间的任何显着差异。 当使用非常高的checkpoint_timeout值时这可能会改变PostgreSQL 9.6现在可以达到1天
## 总结
所以现在你应该知道检查点的目的是什么,也是调整它们的基础。总结一下:
* 大多数检查点应该是基于时间的即由checkpoint_timeout触发
* 性能(不频繁的检查点)和恢复所需的时间(频繁的检查点)
* 15-30分钟之间的数值是最常见的上升到1h也不会有什么不好的9.6甚至能设置为1天。
* 在决定超时之后通过估计WAL的数量来选择`max_wal_size`
* 设置`checkpoint_completion_target`,以便内核有足够的时间将数据刷新到磁盘(但不是非常多)
* 还要调整`vm.dirty_background_bytes`来防止内核在页面缓存中积累大量脏数据