为什么磁盘RT会严重影响vacuum垃圾回收效率?
文中参考文档在github需点击阅读原文打开, 同时推荐2个学习环境:
1、懒人Docker镜像, 已打包200+插件:《最好的PostgreSQL学习镜像》
为什么磁盘RT会严重影响vacuum垃圾回收效率?
https://www.bilibili.com/video/BV15Z4y1d7ny/
声音在空气中的传播速率大约是340m/s, 两个房间里的人讲话感觉不到任何延迟, 你讲一句我马上就能听到并回话. 如果两个人隔了个山头, 你喊一句, 对方可能要隔几秒才能听到.
a: 你吃了吗?
b: 1s后: 吃了
a: 1s后: 吃了啥呀?
b: 1s后: 蛋炒饭
a: 1s后: 蛋炒饭加蛋了吗?
b: 1s后: 你说呢?
如果一次说很多内容再等对方一次回应, 马上就能节省很多时间.
a: 你吃了吗? 吃了啥?
b: 1s后: 吃了,蛋炒饭
a: 1s后: 蛋炒饭加蛋了吗?
b: 1s后: 你说呢?
对于没有上下文判断的交互, 批量操作带来的效果会更加明显. 例如本文接下来提到的vacuum .
磁盘RT其实是个比较大的问题, RT越大, 数据包传输的时延时就越大. 特别是网盘RT比本地盘大很多, 本地的机械盘也比本地SSD的RT大很多.
vacuum 过程:
autovacuum launcher监测系统表(由计数器更新)发现表的垃圾条数或比例达到回收垃圾的阈值, 触发垃圾回收,
扫描数据页从磁盘将其读入shared buffer、或者已经在shared buffer中,
每次读1个page, 而且还是同步IO, 也就是说信号至少一个来回才读到1个page, 你说慢不慢? RT越大这个问题越凸显. 特别是云盘
除非这个表的每个page都在shared buffer中, 否则从磁盘读入是大概率事件.
检查是否有可以被回收的垃圾tuple,
如有则回收之, 从磁盘读上来或者已经存在shared buffer的clean page可能变成dirty page.
也有可能之前这个page在shared buffer中就是dirty page.
最后由bgwriter 调度将dirty page持久化(当然这个部分也可能是checkpoint、backend process干的).
把dirty page写入磁盘又与存储RT有关. 但是它不由vacuum进程去操作.
同时我们也必须要清楚vacuum可能会读入大量数据到shared buffer, 因此shared buffer刷脏虽然是异步的, 但是刷脏慢就会导致shared buffer的clean page或空闲page不足, 还是要等待刷脏, 所以对vacuum也是有性能影响的.
经过以上分析, 磁盘rt大, 给vacuum带来的影响包括:
1、vacuum 从磁盘读取数据到shared buffer的过程慢, 从而影响vacuum速度.
2、或者等待刷脏, 影响vacuum速度(社区版本为异步IO, 影响不大. polardb为DIO,影响大).
社区版本:
目前PG社区版本的内核对RT高带来的vacuum性能问题优化手段较少(除了vm来减少不需要的page scan), 而且单表的vacuum也没有支持并行, 目前的vacuum内核加速包括:
同一个表如果有多个索引, 那么索引的vacuum可以并行. 这个优化针对的是索引多的表.
这也是为什么我建议把经常dml(update,delete,insert, 产生大量垃圾)的表用分区表代替(前提是垃圾回收赶不上产生垃圾的速度了), 因为不同分区做垃圾回收是能并行的.
还有什么优化方法呢?
操作系统有一项设置, 支持IO的prefetch, 就是说一次IO请求可以读取连续的N个扇区, 如果被多读上来的扇区是后续正好要读的扇区, 那么预度就能大大减少到块设备的IO次数(减少pagefault次数).
为了避免随机读也触发预读从而浪费磁盘带宽, linux 内核有个保护机制, 例如连续读n个扇区后才会触发预读. 例如3个扇区.
https://blog.csdn.net/weixin_34138255/article/details/89910895
https://www.hustyx.com/cpp/96
http://tech.hexun.com/2011-03-28/128268656.html
https://www.man7.org/linux/man-pages/man8/blockdev.8.html
https://blog.csdn.net/kunyus/article/details/104620057
blockdev --getmaxsect $device // 块设备每次读请求最大扇区数
blockdev --setra xx $device // 设置块设备的预读扇区数, 0 表示关闭预读
blockdev --getra xx $device // 获得块设备的预读扇区数设置blockdev --getpbsz $device // 获得块设备的物理块(扇区)大小
blockdev --getsize64 $device // 获得块设备的空间大小(字节单位)
blockdev --getsz $device // 获得块设备的空间大小(扇区单位)
操作系统blockdev setra预读的弊端:
仅仅适合顺序IO, 离散IO预读无效果.
块设备全局的设置, 会影响所有的IO行为.
对于这样的情况建议使用8K的block_size, 否则就如上面所说, 很浪费存储带宽.
虽然为了避免随机读也触发预读从而浪费磁盘带宽, linux 内核有个保护机制, 例如连续读n个扇区后才会触发预读. 例如3个扇区, 也就是12K, 当数据库block_size >= 16K时必然会触发预读, 导致浪费磁盘带宽(也包括网盘的网络带宽)从而瓶颈变成带宽瓶颈.
除此以外, 还可以想想有没有这样的系统接口, 能够支持一次请求多个离散的IO, 同样也能减少RT, 并且避免全局的设置.
writev readv
PolarDB:
内核层面支持预读功能, 新增函数:polar_bulk_read_buffer_common() 尝试读多个buffers。
polar_bulk_read_size可以调,session级的参数.
同时支持结合VM判断不需要vacuum的page ID进行skip, 避免不必要的预读.
设置polar_bulk_read_size=512KB(最大预读大小) 优化结果,对于单个vacuum进程:
CPU 使用率 从10%提升到 54%
vacuum处理速度,从 60MB/s 提升到 380 MB/s(RDS_PG是 400 MB/s 左右(本地盘, RT低)) polardb是网盘, RT高于本地盘.
PolarDB 预读对什么场景有加速效果? 共同特点: 都是几乎全表顺序扫描的场景、几乎都是访问全表、几乎都是同步IO.
heap scan、
vacuum、
create index
扩展知识1:
网盘(磁盘)RT高还会对哪些场景的性能影响较大, 问题如何解决?
checkpoint, bgwriter都是后台进程刷脏, 相对用户是异步(不影响用户请求). 但是在写多的场景, 可能刷脏处理不及时, 导致backend申请不到shared buffer, 需要等待刷脏腾出buffer, 那么一样会影响写性能.
这种情况建议调高bgwriter刷脏调度频率(一个周期内刷出更多脏页). 怎么判断呢? 从统计信息里判断,backend process是不是在大量主动刷脏, 是的话就要调高bgwriter刷脏速度了.
backend process 申请buffer时遇到没有空闲page或clean page的情况, 需要等待dirty page刷脏.
SQL有未命中buffer的IO请求, 需要等待从磁盘读取数据.
事务提交慢(需要持久化wal, 相当于同步IO). 需要等待写数据到磁盘.
数据库崩溃恢复, 需要从wal和datafile中读取对应page进行恢复, 都是同步IO. (polardb采用异步恢复解决这个问题)
《一起学PolarDB - 第7期 - 为什么数据库越大崩溃恢复越慢?》
参考:
《一起学PolarDB - 第8期 - 为什么存储RT指标对实例性能影响很大?》
扩展知识2:
与网盘RT高类似的, 网络RT高的影响场景和优化方式:
《PostgreSQL 15 preview - libpq协议新增H消息, 通知DB server flush output buffer in pipeline mode.》
《PostgreSQL 14 preview - libpq驱动 支持pipeline 模式, SQL请求支持异步化通信, 大幅度提升性能, 降低RT》
优化方式:
网络RT高通常采用批量的方式(增加单趟传递的信息量, 避免等待回包的时间)、
加大连接并发、
或者异步消息(不需要等待回包)来提高处理吞吐量.
本期问题1:
网络RT高对哪些场景的性能有非常明显的影响?
a. 高并发小事务
b. 计算量或者IO量很大的复杂慢SQL
c. 大量 简单insert 请求
d. 大量 简单select 请求
答案:
acd
解释:
参考本文内容
本期问题2:
磁盘或存储的RT高对哪些场景的性能有非常明显的影响?
a. 计算很复杂大量耗费CPU的慢SQL
b. 大量IO都在shared buffer中命中的SQL
c. 大量IO都未在shared buffer以及page cache中命中的SQL
d. 小事务, 并且申请shared buffer时发现缺少clean page或free page, 需要驱出dirty page腾挪空间.
e. 高并发小事务
f. 数据库崩溃恢复
答案:
cdef
解释:
参考本文内容
本期问题3:
为什么磁盘RT会严重影响vacuum垃圾回收效率?
a. vacuum 需要扫描表和索引, 磁盘IO的动作很频繁
b. vacuum 回收垃圾是同步IO, 每个页面修改完成后需要同步持久化到磁盘.
c. vacuum 时需要把磁盘中的page读到shared buffer, 如果shared buffer没有足够的clean page或free page, 会导致驱出dirty page的动作(PG 社区版本这个动作是异步IO), 产生额外IO.
d. vacuum 扫描时每次只读取1个page , 每两个请求之间都夹着一段RT时间.
答案:
acd
解释:
参考本文内容
本期问题4:
社区版本对Vaccum有哪些优化?
a. 支持调大vacuum内存空间, 一次性存储一张表所有扫描到的dead tuple id, 避免索引垃圾回收时需要重复扫描索引
b. 单个表的多个索引可以开启多个work process执行索引的并行vacuum
c. 使用visibility map, 直接跳过干净的page
d. 单个表的多个数据文件支持多个work process并行执行vacuum
答案:
abc
解释:
参考本文内容
本期问题5:
社区版本为什么目前无法优化vacuum heap scan的效率?
a. 完全clean的heap page在vacuum heap scan时也无法被跳过
b. 单个表的多个数据文件不支持开启多个work process并行执行vacuum
c. vacuum一次只扫描1个page, 每两个page之间都夹着1个RT的时间片段
d. vacuum 进程的逻辑复杂, 大量时间耗费在dead tuple的判断和回收, 无法跑满磁盘带宽.
答案:
bc
解释:
参考本文内容
本期问题6:
对大量顺序IO的场景, 因为RT高引起的性能问题, 操作系统层面有什么高性价比的优化手段?
a. 增加内存, 避免访问磁盘
b. 更换磁盘, 使用本地SSD
c. 设置块设备预读参数, 一次IO请求返回多个连续扇区的内容.
d. 使用RDMA网络的云存储
答案:
c
解释:
参考本文内容
本期问题7:
对大量顺序IO的场景, 因为RT高引起的性能问题, 操作系统层面开启块设备预读后有哪些弊端?
a. 开启块设备预读仅适合离散IO, 不适合顺序IO的场景.
b. 离散IO触发预读时会导致存储带宽浪费, 甚至导致存储带宽瓶颈
c. 全局设置, 影响所有的IO行为.
d. 当数据库block size大于预读保护机制阈值时, 所有的IO都会产生预读, 导致存储带宽严重浪费.
答案:
bcd
解释:
参考本文内容
本期问题8:
polardb 在内核层面进行了什么优化来提升vacuum的性能, 使得vacuum接近本地盘性能
a. 支持通过VM来判断不需要进行垃圾回收的page, 跳过扫描减少IO, 同时可以避免无效的预读
b. 支持单一数据文件的并行vacuum
c. 支持内核层面的预读配置, 可以在会话层开启, 避免影响所有的IO请求case
d. 单个表的多个数据文件支持并行vacuum
答案:
ac
解释:
参考本文内容
本期问题9:
PolarDB 预读对什么场景有加速效果?
a. 垃圾回收
b. 全表扫描
c. 索引扫描
d. 创建索引
答案:
abd
解释:
参考本文内容
本期问题10:
PolarDB 预读的加速场景有什么共性?
a. 访问的数据量都偏少
b. 都是顺序IO
c. 几乎都要访问整张表的所有数据
d. 几乎都是同步IO
答案:
bcd
解释:
参考本文内容
欢迎关注我的github (https://github.com/digoal/blog) , 学习数据库不迷路.
近期正在写公开课材料, 未来将通过视频号推出, 欢迎关注视频号:
文章中的参考文档请点击阅读原文获得.