1、full_page_write是什么?
官网中,对它的解释是:
当这个参数打开时,PG服务器在一个检查点之后的页面的第一次修改期间将每个页面的全部内容写到wal中,这么做是因为在操作系统崩溃恢复期间正在处理的一次页写入可能只有部分完成,从而导致在一个磁盘页面中混合有新旧数据。在崩溃后的恢复期间,通常存储在wal中的行级改变数据不足以恢复这样一个页面。存储完整的页面映像可以保证页面被正确的存储,但是代价是增加了必须被写入wal的数据量。
可参考:https://postgresqlco.nf/doc/zh/param/full_page_writes/
2、怎么理解上面说的“在操作系统崩溃恢复期间正在处理的一次页写入可能只有部分完成,从而导致一个磁盘页面中混合有新旧数据”?
PG数据页写入是以page为单位,默认是使用8KB的页面大小。但是存储堆栈的其他部分可能会使用不同的块大小。Linux文件系统通常使用4kb的大小,在硬件级别上,旧的驱动器使用512B扇区,新设备可能会以更大的块(4kb或者8kb)写入数据。
因此,当PostgreSQL写8kb页面时,存储堆栈的其他层可能会把它分成更小的块,分别管理。
这样就产生了原子性的问题。8kb的postgreSQL页面可以分成两个4kb的文件系统页面,然后再分成512b扇区。如果在写入期间,服务器崩溃(电源故障、内核错误)那么就可能造成数据库写入了8kb的数据页,但是在崩溃前只有部分数据写入了磁盘。
3、上面提到的丢失的部分数据,可以从wal日志中恢复吗?
不可以。我在之前的文章中有提到,wal主要用于数据库崩溃时的数据恢复,在崩溃恢复时,数据库将读取自上次检查点之后的wal日志,并再次应用更改,以确保数据文件修改完成。但是这里有一个问题 ——恢复并不是盲目地应用修改,它通常需要先读取数据页,此时数据库是假定页面没有被损坏的。这其实是自相矛盾的,因为要修改数据损坏,先假定没有数据损坏。
4、那么怎么解决这种部分写的问题呢?
全页写是解决这种难题的一种方法——当在检查点之后第一次修改一个页面时,整个页面都被写入wal。这保证了在恢复过程中,接触到一个页面的第一个wal记录包含了整个页面,而不需要从数据文件中读取可能被破坏的页面。
对应到数据库参数的配置上就是设置full_page_write=on。
5、打开full_page_write有什么影响?
其实官网上已经提到打开full_page_write的代价是增加了必须被写入wal的数据量。也就是wal的写放大问题。这样会增加额外的IO和磁盘消耗。
例如:改变一个8kb页面时一个字节,却需要把整个页面记录到wal中。
6、怎么减少全页写的影响?
大多数全页面操作是发生在一个检查点之后,直到下一个检查点。所以重要的是调整checkpoint的频率,不要太频繁。
7、打开full_page_write是不是一定是必须的?或者有没有什么办法避免部分写?
如果硬件可以减少减少页面写入的风险、或者文件系统支持原子性,或者如果PG使用更小的页面(4kb), 可以考虑将其关闭。(待验证)
参考:
https://www.2ndquadrant.com/en/blog/on-the-impact-of-full-page-writes/
https://developer.aliyun.com/article/237
https://developer.aliyun.com/article/463951
最近搞了一个公众号PostgreSQL运维技术,欢迎来踩~
悄悄放一张:
PostgreSQL运维技术