暂无图片
暂无图片
5
暂无图片
暂无图片
暂无图片

PostgreSQL的"double buffers"刷脏机制和参数

原创 阎书利 2022-12-15
1157

PostgreSQL数据库使用双缓存写数据,shared_buffer + OS page cache,下图是PG与OS内存交互的过程 ,在PostgreSQL中,shared_buffers所代表的内存区域可以看成是一个以8KB的block为单位的数组,即最小的分配单位是8KB。这正好是一个page的大小,每个page以page内部的元数据(Page Header)互相区分。这样,当Postgres想要从disk获取数据(page)时,他会(根据page的元数据)先搜索shared_buffers,确认该page是否在shared_buffers中,如果存在,则直接命中,返回缓存的数据以避免I/O。如果不存在,再到OS缓存查找,最后才会通过I/O访问disk获取数据。

对于double buffer也可参考我这篇文章
https://www.modb.pro/db/523889

本篇文章,结合“double buffers”机制分析一下影响buffers刷脏页的相关机制和参数。

1671009408655.png

一、刷脏页之前记录WAL日志文件(redo)

一个PostgreSQL后端进程产生数据写入后,一定会先写入WAL日志。

1671004656782.png

而WAL日志也不是凭空产生的,它是将wal buffer里的数据持久化到磁盘的,大致的流程基本如下

通过一些接口注册wal数据
          ⬇
将注册的wal数据重组为一个数据链表
          ⬇
将数据链表中的数据拷贝到wal页buffer中
          ⬇
将wal页buffer刷写到磁盘中 

1671010337890.png

而对于这套流程里将记录存储到wal日志文件的主要函数 ,主要有以下几个:

XLogBeginInsert(); 
XLogRegisterData(); 
XLogRegisterBuffer(); 
XLogRegisterBufData();
XLogSetRecordFlags();  
XLogInsert();  
XLogRecordAssemble();  
XLogInsertRecord(); 
PageSetLSN   

在写完了wal日志后,数据库可以进行刷脏页。

二、数据库层刷脏

PostgreSQL支持一种称为全页写(full page write)的功能来处理部分写(页裂)问题。如果启用(默认启用),PG会在每个检查点之后、每个页面第一次发生变更时,将头数据和整个页面作为一条XLOG记录写入WAL缓冲区,然后再进行刷脏。

1.数据库刷脏页方式

1.bgwriter刷脏:后台刷不影响用户使用,但从全局上看可能会有单页多次重复刷的现象

2.checkpoint刷脏:阻塞性刷脏,严重影响QPS,但从全局上看可以等单页写多次,减少刷脏页的次数 。

checkpoint是以特定的时间间隔刷新所有脏页,并创建一个用于数据库恢复用的检查点 。而Bgwriter定期把脏数据从内存缓冲区刷出到磁盘中,减少查询时的阻塞 ,因为PostgreSQL在定期作检查点时需要把所有脏页写出到磁盘,通过BgWriter预先写出一些脏页,可以减少设置checkpoint时要进行的IO操作,以便始终有足够多的干净页面可以使用,使系统的IO负载趋向平稳 。两者的目的和执行频率都有不同。

Bgwriter使用的缓存替换算法在8.1版本之前是用的LRU,从8.1版开始,PostgreSQL使用了Clock Sweep(时钟扫描)。 clock sweep 算法的思想是根据访问次数来判断哪些数据为热点数据。当缓存空间不足,它会优先替换掉访问次数低的缓存 ,即缓存使用频率高的会被保存,低的被移除。

2.相关参数或机制

1.bgwriter相关

bgwriter_delay :bgwriter每次工作之后休息的间隔,休息避免阻塞用户线程。 在每一轮循环中,background writer都会为一定数量的脏缓冲区发出写操作,然后background writer进行睡眠,睡眠的时间为bgwriter_delay参数值,然后再唤醒,然后重复。设置bgwriter_delay为不是10的倍数的值可能与将其设置为10的下一个更高的倍数具有相同的结果。

bgwriter_flush_after :让bgwriter时不时的触发OS写dirty page 。当 background writer 写入的数据量超过本参数值之后,强制OS做一次FLUSH 。这么做会限制 kernel 中 page cache 的 dirty data 的数量,减少在 checkpoint 最后 fsync 时发生卡顿的可能性。 因为OS层可能要累积到一个较大值才会去写盘 此时可能导致较大的写盘IO动作, 从而争抢用户的IO 。但是bgwriter过于频繁让OS写磁盘也会有问题,例如shared buffer中的page在os层刷脏期间多次被标记为脏块,或者相邻的page在一个刷脏窗口内变脏并且被bgwriter多次写出,bgwrite频繁触发fsync ,也会增加磁盘的IO。

OS也有自己的IO调度策略,可以设置dirty_background_bytes为一个较小值, 避免大的IO。

bgwriter_lru_maxpages一个bgwriter工作周期内, 最多刷出多少个dirty page。每轮 bgwriter 进程写入 LRU Page 的最大数量。本参数设置为零会禁用 background writing (bgwriter)行为,但是 background writer 进程依然会存在

bgwriter_lru_multiplier一个bgwriter工作周期内, 应该刷出多少个dirty page。 在每轮中写出的脏缓冲区的数量基于最近几轮中服务器进程需要的新缓冲区的数量。最近的平均需求乘以bgwriter_lru_multiplier,以估计下一轮所需的缓冲区数量。脏缓冲区被写出,直到相应的数量的缓冲区为止。(每轮写入的缓冲区数量不会超过bgwriter_lru_maxpages。)。1.0的设置表示一个“及时”的策略,即准确地写入预测需要的缓冲区数量。较大的值可以缓冲需求的峰值,而较小的值有意让服务器进程完成写操作。默认值是2.0。

2.checkpoint相关

Checkpoint的工作内容:

1.Flush Dirty Pages,刷脏
2.Update some points,更新XlogCtl和ControlFile,并持久化至pg_control文件
3.Remove old wal,计算两次checkpoint间的WAL数量进行回收重用,并清理不再需要的WAL

如下为一些定义的Checkpoint触发的条件,是xlog.h头文件里定义的场景,会直接引起CreateCheckPoint和相关附属行为 ,有的包含了刷脏的流程。

image.png

需要注意的是,当非停库、redo完成、强制触发checkpoint时,如果数据库没有写入操作,则直接return不进行Flush dirtypage等操作 。也就是所谓的Checkpoint skipped机制。

三、系统层刷脏

OS缓存使用简单的LRU(移除最近最久未使用的缓存)

对于系统层的刷脏而言。对于这几个系统参数也有一个大致的分类。

1671002572994.png

1.后台异步

vm.dirty_background_bytes = 0  # 触发回刷的脏数据量。类似postgresql的bgwriter, 由后台进程而不是用户进程刷
vm.dirty_background_ratio = 5   触发回刷的脏数据占可用内存的百分比,5表示5%

2.前台阻塞刷脏

vm.dirty_bytes = 0 # 触发同步写的脏数据量。类似postgresql 的 server process刷脏, 用户进程参与, 所以会导致用户进程的RT升高
vm.dirty_ratio = 10  #触发同步写的脏数据占可用内存的百分比。是可以用脏数据填充的绝对最大系统内存量,当系统到达此点时,必须将所有脏数据提交到磁盘,同时所有新的I/O块都会被阻塞,直到脏数据被写入磁盘。这通常是长I/O卡顿的原因,但这也是保证内存中不会存在过量脏数据的保护机制。
vm.dirty_writeback_centisecs = 500 # 回刷进程定时唤醒时间。后台进程的调度间隔。指定多长时间 pdflush/flush/kdmflush 这些进程会唤醒一次,然后检查是否有缓存需要清理。
vm.dirty_expire_centisecs = 100 # 指定脏数据超时回刷时间(单位:1/100s),也就是脏数据能存活的时间。在page cache中存活时间超过这个值的脏页才会被刷盘。当 pdflush/flush/kdmflush 在运行的时候,他们会检查是否有数据超过这个时限,如果有则会把它异步地写到磁盘中。毕竟数据在内存里待太久也会有丢失风险。
  • XXX_ratio和 XXX_bytes是同一个配置属性的不同计算方法。如果设置_bytes版本,则_ratio版本将变为0,反之亦然。,优先级 XXX_bytes > XXX_ratio

四、如何确认缓存数据量和缓存大小

在访问数据时,数据会先加载到os缓存,然后再加载到shared_buffers,这个加载过程可能是一些查询,也可以使用pg_prewarm预热缓存,以获取一个好的查询效果。

如下两种方式可以查看缓存的占用

1.pg_buffercache和pgfincore

可以使用pg_buffercache和pgfincore这两个插件去查看缓存的数据量。可参考我这篇文章里的相关使用
https://www.modb.pro/db/523889

2.hcache

除此之外可以使用hcache,hcache是一款查看buff/cache的工具,可以查看进程的缓存占用情况,以及占用操作系统缓存最多的N的文件 。

安装 方式如下

wget https://silenceshell-1255345740.cos.ap-shanghai.myqcloud.com/hcache 
chmod +x hcache
mv hcache /usr/local/sbin/ 

使用方式

  • 查看使用缓存最多的10个文件 ( hcache -top 10)
  • 查看列出的文件详细( lsof filename)
  • 查看进程的缓存使用 (hcache -pid xxxx)

image.png

五、如何释放shared_buffers和OS caches

注意:在生产环境释放缓存可能造成很大的影响。严禁随便执行。可供测试环境自己测试

1.如何释放shared_buffers

重启数据库可以释放shared_buffers。

2.如何释放OS cache

echo 1 > /proc/sys/vm/drop_caches

drop_caches的值可以是0-3之间的数字,代表不同的含义:

0:不释放(系统默认值)
1:释放页缓存
2:释放dentries和inodes
3:释放所有缓存

释放完内存后改回去让系统重新自动分配内存。

echo 0 >/proc/sys/vm/drop_caches

free -m #看内存是否已经释放掉了。
如果需要释放所有缓存,就输入下面的命令:

echo 3 > /proc/sys/vm/drop_caches
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论