为什么PG有Double Cache?
文章开始前推荐2个学习环境:
1、欢迎使用镜像快速体验PostgreSQL/DuckDB强大功能:《最好的PostgreSQL学习镜像》
2、欢迎使用云起实验室: 《免费体验PolarDB开源数据库》
3、PolarDB开源数据库内核、应用等学习图谱: https://www.aliyun.com/database/openpolardb/activity
为什么PG有Double Cache?
https://www.bilibili.com/video/BV1km4y1D7BW/
社区版本:
《DB吐槽大会,第6期 - PG Double Cache》
PG在读、写数据文件时, 使用了Buffer IO的系统调用, 因此会用到Page Cache, 再加上数据库自身的shared buffer, 实际上占用了两份内存.
1、存在浪费内存的现象.
2、多一次内存拷贝的动作, 访问路径变长, 增加了读操作延迟. 影响比较大的可能是大范围数据扫描(OLAP场景).
3、数据库的buffer命中率数据将不准确, 无法判断未命中的block是从page cache读取的还是从块设备中读取的. 使得PG 系统表中命中率的统计参考意义不大.
扩展知识:
1、了解了double cache的问题, 那么为了快速的释放page cache, 可以安装pgfincore插件, 设置open file 的system call的flag为优先释放(对于冷数据). 当然也可以设置为尽量不释放的flag(对于热数据). 这个方法需要业务自己判断, 熟悉哪些是热表哪些是冷表.
2、wal 文件的IO操作略有不同: 可以自动选择DIO或者bufferIO. 当开启了归档、或者存在下游流复制节点时采用bufferIO, 否则使用DIO. 为什么呢?
因为下游流复制节点通常会立即读取刚写入的WAL内容, 采用bufferIO可以避免从块设备读取WAL内容. 归档的原因一样.
没有下游节点时, 使用DIO可以绕过page cache, 省去再调用fsync刷盘的操作.
wal receiver进程接收到wal后也不会使用DIO来写wal文件, 因为startup进程立马就要读出来进行恢复. (除非配置了延迟恢复到参数, 但是这个PG代码没有进行区别对待, 只要是wal receiver写WAL都会使用buffer io.)
3、page cache并不是一无是处:
重启数据库时, page cache中还有热数据. 所以虽然重启后数据库shared buffer中没有热数据, 但是由于数据可能存在page cache中, 就算立即有高并发的访问, 性能抖动也不会太厉害.
bgwriter 采用异步IO, 在性能较差的块设备上, append only的写性能依旧可以很好而且很平顺. ( 指: 开启异步wal、或者group commit的情况下. 避免WAL性能问题带来的测试影响. )
bgwriter 采用bufferIO时, 一个block如果被更新多次并且刷出去多次, 但是在操作系统层面持久化到物理存储的次数可能更少, 可以间接减少物理IO的调用次数.
操作系统层面会采用IO合并, 例如连续的block可能一次刷出, 减少IO次数.
PolarDB:
1、采用DIO, 是选择, 也是架构使然. 因为采用计算存储分离架构, DIO更便于控制数据刷盘的动作, RW节点只要专注数据库自己的shared buffer写出机制, 而不需要担心操作系统的background flush操作扰乱刷脏机制.
2、怎么解决重启实例后性能抖动的问题? 做了持久化内存池. 在数据库重启时BufferPool并不销毁,如下图所示:crash和restart期间BufferPool不销毁。
内核中的共享内存分成2部分:
全局结构,ProcArray等。
BufferPool结构;其中BufferPool通过具名共享内存来分配,在进程重启后仍然有效。而全局结构在进程重启后需要重新初始化。
BufferPool中并不是所有的Page都是可以复用的,比如:在重启前,某进程对Page上X锁,随后crash了,该X锁就没有进程来释放了。因此,在crash和restart之后需要把所有的BufferPool遍历一遍,剔除掉不能被复用的Page。另外,BufferPool的回收依赖k8s。该优化之后,使得重启前后性能平稳。
参考: https://github.com/ApsaraDB/PolarDB-for-PostgreSQL/blob/main/doc/PolarDB-CN/Architecture.md
本期问题1:
下面哪个插件可以控制page cache的释放机制: 优先释放、尽量不被释放?
a. pg_hint_plan
b. pgfincore
c. pageinspect
d. pg_buffercache
答案:
b
解释:
pgfincore插件, 设置open file 的system call的flag为优先释放(对于冷数据). 当然也可以设置为尽量不释放的flag(对于热数据). 这个方法需要业务自己判断, 熟悉哪些是热表哪些是冷表.
本期问题2:
下面哪些是double cache带来的问题?
a. 写性能下降
b. 浪费内存
c. 从磁盘读大量数据的性能下降
d. 内存命中率显示不准确
答案:
bcd
解释:
1、存在浪费内存的现象.
2、多一次内存拷贝的动作, 访问路径变长, 增加了读操作延迟. 影响比较大的可能是大范围数据扫描(OLAP场景).
3、数据库的buffer命中率数据将不准确, 无法判断未命中的block是从page cache读取的还是从块设备中读取的. 使得PG 系统表中命中率的统计参考意义不大.
本期问题3:
PolarDB采用DIO后, 怎么解决重启实例shared buffer清空导致的性能抖动问题?
a. pg_prewarm
b. 持久化bufferpool
c. 实时记录热数据元信息, 启动时自动加载热数据
d. 内存镜像
答案:
b
解释:
PolarDB 做了持久化内存池. 在数据库重启时BufferPool并不销毁,crash和restart期间BufferPool不销毁。
本期问题4:
WAL日志什么时候会采用DIO?
a. pg_basebackup 接收wal
b. wal sender
c. 没有开启wal sender、没有使用归档、非wal receiver进程
d. 使用windows时
答案:
c
解释:
wal 文件的IO操作略有不同: 可以自动选择DIO或者bufferIO. 当存在下游流复制节点时采用bufferIO, 否则使用DIO. 为什么呢?
因为下游流复制节点通常会立即读取刚写入的WAL内容, 采用bufferIO可以避免从块设备读取WAL内容.
没有下游节点时, 使用DIO可以绕过page cache, 省去再调用fsync刷盘的操作.
wal receiver进程接收到wal后也不会使用DIO来写wal文件, 因为startup进程立马就要读出来进行恢复. (除非配置了延迟恢复到参数, 但是这个PG代码没有进行区别对待, 只要是wal receiver写WAL都会使用buffer io.)
src/backend/access/transam/xlog.c /*
* Optimize writes by bypassing kernel cache with O_DIRECT when using
* O_SYNC/O_FSYNC and O_DSYNC. But only if archiving and streaming are
* disabled, otherwise the archive command or walsender process will read
* the WAL soon after writing it, which is guaranteed to cause a physical
* read if we bypassed the kernel cache. We also skip the
* posix_fadvise(POSIX_FADV_DONTNEED) call in XLogFileClose() for the same
* reason.
*
* Never use O_DIRECT in walreceiver process for similar reasons; the WAL
* written by walreceiver is normally read by the startup process soon
* after it's written. Also, walreceiver performs unaligned writes, which
* don't work with O_DIRECT, so it is required for correctness too.
*/
欢迎关注我的github (https://github.com/digoal/blog) , 学习数据库不迷路.
近期正在写公开课材料, 未来将通过视频号推出, 欢迎关注视频号:
文章中的参考文档请点击阅读原文获得.