天天看点

PostgreSQL 9.6 检查点柔性优化(SYNC_FILE_RANGE) - 在单机多实例下的IO Hang问题浅析与优化

postgresql检查点是将shared buffer中的脏页打标记,并集中将其刷到磁盘的动作(fsync)。(期间可能有刷盘的调度,降低当脏页很多时带来的io影响)

在检查点之外,平时bgwriter进程则会使用bufferio的方式(write)将脏页写到os的dirty page。

如果shared buffer非常大,而且数据库应用如果是频繁产生脏页的应用,那么检查点带来的性能影响会非常的明显。

例如shared buffer有100g,活跃数据有100g,同时活跃数据在不停的被update(产生脏页),那么在发生检查点时,fsync的过程中,可能导致性能急剧下降。

接下来重现一下以上问题。

单机开启100个pg实例,每个实例限制一定的内存,cpu,以及io资源,其中日志盘iops限制4000,数据盘iops限制800。

压测方法

每个实例最大数据量1亿,对数据进行随机的upsert操作。

因此全表都是热点。

每个实例连4个连接,同时进行压测。

测试用例参考

<a href="https://github.com/digoal/blog/blob/master/201609/20160927_01.md">20160927_01.md</a>

由于同时开启测试,每个节点几乎在同一时间点进入检查点状态。

产生大量的writeback内存。

通过以下方法可以观察到

解释

在产生了大量的writeback内存计数后,最后检查点调用fsync前,因为脏页没有完全落盘,导致实例的检查点在fsync的阶段需要耗费自己的iops进行刷盘,非常慢。

甚至实例完全不可用。

观察到的现象

数据库整机io很低(只有数据盘的io,并且受到cgroup限制),

tps降到0 (更新块被堵塞) ( shared buffer中没有剩余的块? )

需要等待实例的writeback 全部刷盘后才能恢复。

期间进程状态如下

状态解释

进程stack信息

checkpointer进程

stats收集进程

bgwriter进程

backend process 进程

logger进程

wal writer进程

文件系统已使用data=writeback挂载

postgresql 9.6的检查点改进如下

1. 阶段1(调用write + 检查点调度)

2. 阶段2(调用sync_file_range)

实际上通过设置os调度也能缓解,例如。

3. 阶段3(fsync)

分析

1. 从检查点源码开始

2. 调用buffersync

3. 调用synconebuffer

4. 调用flushbuffer

5. 调用mdwrite

6. 调用filewrite

调用write产生dirty page

7. 调用schedulebuffertagforwriteback

8. 调用issuependingwritebacks

作用见阶段2。

9. 调用issuependingwritebacks

10. 调用smgrwriteback

src/backend/storage/smgr/md.c

11. 调用filewriteback

12. 调用pg_flush_data

src/backend/storage/file/fd.c

(前面已经调用了write,现在告诉os 内核,开始将脏页刷到磁盘)

注意,如果range指定的脏页很多时,sync_file_range的异步模式也可能被堵塞。

调用sync_file_range

异步模式

1. 以上动作做完后,操作系统不一定把dirty page都刷盘了。

因为调用的是异步的sync_file_range。

2. 同时在此过程中,bgwrite, backend process还有可能将shared buffer中新产生的脏页写入os dirty page。

这些脏页也许涉及到接下来检查点需要fsync的文件。

13. 接下来, 检查点开始调用smgrsync

开始fsync文件级别,如果文件又产生了脏页怎么办(见以上不稳定因素分析)。

14. 调用mdsync

15. 调用filesync, 同步整个文件

16. 调用pg_fsync

17. 调用pg_fsync_no_writethrough

18. 调用 fsync 刷盘

1. 调用fsync前,操作系统不一定把dirty page都刷盘了。

因为这两个不安定因素的存在,同时加上环境中有多个pg实例,并且每个pg实例都限制了较小的data盘io,导致fsync时刷盘非常的慢。

redo的io能力远大于data盘的io能力时,checkpoint过程中可能又会产生很多热点脏页。

导致检查点在最后fsync收官时,需要刷dirty page,而同时又被实例的cgroup限制住,看起来就好像实例hang住一样。

是在write阶段进行调度,在sync_file_range和fsync过程中都没有任何调度。

PostgreSQL 9.6 检查点柔性优化(SYNC_FILE_RANGE) - 在单机多实例下的IO Hang问题浅析与优化

1. 解决不安定因素1 - 避免检查点过程中产生未刷盘的dirty page

在检查点过程中,bgwriter或backend process从shared buffer产生的脏页write out时,会调用write即buffer io。

进入检查点后,bgwriter或backend process从shared buffer产生的脏页write out时,同时记录该page的id到list(1或2)。

2. checkpoint在最后阶段,即调用fsync前,插入一个阶段。

将list(1或2)的page实行sync_file_range,等待其刷盘成功。

使用以下flag

3. 为了防止bgwrite或backend process 与checkpoint 的sync file range冲突。

使用两个list来交替记录检查点开始后的shared buffer evict pages。

4. 新增一个guc变量,配置当checkpoint最后一次sync file range的list page树少于多少时,进入fsync阶段。

允许用户根据iops的规格,配置这个guc变量,从而减少最后fsync时需要等待的page数。

注意这个值也不能设得太小,否则可能造成漫长的很多轮list1和list2的sync file range过程。

需要修改postgresql内核,动作较大。

5. 解决不安定因素2 - 检查点最后的阶段,调用fsync前,确保fd的所有dirty page都已经write out。

目前checkpoint调用的pg_flush_data是异步的sync_file_range,我们需要将其修改为同步的模式。

建议只修改checkoint的调用,不要动到原有的逻辑。

6. 从os内核层面解决io hang的问题。

阿里云rds for postgresql已从数据库内核层面完美的解决了这个问题,欢迎使用。

1. 检查点柔性调度,减少fsync时的dirty pages,降低stall。

checkpoint_flush_after (integer)

2. bgwrite bufferio柔性调度,降低stall。

bgwriter_flush_after (integer)

3. 当shared buffer中没有空闲页或者干净的页时,backend也会evict dirty page。 这是对backend process的调度。

backend_flush_after (integer)

4. 这个和shared buffer无关,是wal buffer的柔性调度。

wal_writer_flush_after (integer)

<a href="http://yoshinorimatsunobu.blogspot.com/2014/03/how-syncfilerange-really-works.html">http://yoshinorimatsunobu.blogspot.com/2014/03/how-syncfilerange-really-works.html</a>

<a href="http://info.flagcounter.com/h9v1">count</a>