PG FULL_PAGE_WRITES MYSQL DOUBLE WRITE LOG

 白鳝的洞穴

执行begin backup之后,oracle会把将要备份的数据文件都标记为hot-backup-in-progress,锁定所要备份的datafile header的scn,例如此时scn=100,同时redolog中会记住这个scn,其他数据文件正常使用,scn会继续增长。之后在备份所要备份的数据文件过程中,数据文件是允许写入和checkpoint,而且可能不止一次checkpoint,而这个过程中的所有操作和checkpoint也会正常记录到redolog与archivelog中。

oracle系统数据文件最小存储单元是数据块,比如8192bytes,而操作系统的最小存储单元os快为固定的512bytes,这样问题就产生了。

在oracle执行begin backup之后进行copy操作,这个copy操作底层属于操作系统的命令,每次只能copy一个os block,假如oracle数据库的block单位为8192bytes,那么这个oracle block就由16个os block组成,这里为了方便理解,我们把他们标记为1--16个os block。copy命令对于数据块的拷贝时没有顺序的,也就是说第一次可能copy 1号block,而第二次可能就会copy 16号block。而在这个copy过程中,对于oracle热备份机制来说对oracle整个的block是允许读写,这样就会产生如下的问题,例如:执行begin backup,oracle锁定datafile header的scn,假设此时oracle block中存储的数据是10,敲copy进行复制,系统就会将这个oracle block中的16个os block一个一个地拷贝到备份目录,假如拷贝到第五个os block时候,如果有数据写入,例如需要将这个数据块中的数据update为20,oracle就会调用dbwr进程对这个数据块进行数据修改,同样dbwr进程也是不顺序地将数据写入这16个os block,所以他就有可能从已经拷贝完的那五个os块中开始写数据,也有可能从剩下的11个os block中开始写数据。如果从剩下的11个os block中开始写入的话,就会带来一个严重的后果,热备份copy正在进行,而剩下的11个block其中的几个有可能数据已经改变,这样copy出来的备份文件肯定会不一致,copy出来的备份文件对于这个oracle block来说前5个block是原来存储数据10的信息,而后来copy的11个block就有可能存储的是update之后的数据20的信息,这样是绝对不允许的。

因此,oracle做了这样的一个机制,在copy过程中如果需要有数据update,dbwr进程告诉oracle我要update,这时候oracle就会通知备份系统,先把所要写入的那个oracle block完全镜像到redo中,redo记录的是整个数据块的镜像,而不是一条信息(改变向量)。之后dbwr在开始向这个oracle block中的16个os block随机写入数据。这样,在数据恢复的时候,oracle检查是从被锁定的那个scn时刻起开始恢复,如果检查到那个时间点上的某个oracle block出现上述所说的那种“损坏的block”,他就会将redo中的镜像再完全copy到这个oracle block,这样,这个数据块就是begin backup的那个时间点时候的完整的数据块了,之后redo就可以从这个scn进行向后的恢复工作。

这个过程也就解释了为什么在热备过程中有时候redo会急剧增加的现象。

结束热备份end backup之后,oracle解锁备份的datafile header的scn,自动同系统当前的scn同步,例如此时的scn已经变为1000,那么备份文件的scn会与系统自动同步到1000。

因为在备份过程中数据文件及redo是允许写入的,因此备份的数据文件不仅包含scn=100以前的数据,而且还包含scn在100和1000之间的所有DML后数据。这就是我上述所说的“损坏的block”。

在数据恢复的时候,将备份的数据文件复制到oracle系统之后,因为备份文件的scn是在执行alter database begin backup时候的时间点,也就是scn=100,oracle会在redolog中查找这个scn,如果这个scn时间点有“损坏的block”(修改的db block非OS block),redo先把以前镜像好的block完全复制回去,然后自动执行scn大于100之后的所有操作及数据,写入当前数据文件。而从备份文件恢复到当前的数据文件中scn在100和1000之间的数据将会被redo操作覆盖。(REDO增加的量也还好,就是第一次修改数据块的全db buffer镜像(类似PG中的FULL_PAGE_WRITES),后面当成正常的redo操作了。)

 

产生较多的REDO记录是由热备引起的,因为在热备过程中,我们采用copy/ocopy命令,这个是属于操作系统的命令,他和Oracle是不相关的,不能和Oracle的内部进程如dbwr进行交互,这样就可能导致热碑块的出现,因为操作系统读取数据文件时,他的IO尺寸并不是block size的大小,一般会更小,这样会导致一个数据块被读取多次,而每次获取的部分都不一致(数据不断更新),为了恢复这种断裂的热备块,Oracle进行了数据块前映象这个操作,对于backup模式的数据文件块,在第一次受到DML影响时,先将数据块整个COPY到REDO中,后续的DML在进行UNDO,正常REDO信息的记录,当恢复数据文件时,会先应用最先的数据块前映像,然后才是后续的REDO记录信息,更多的日志记录就是这个前映像产生的,这个不能和UNDO弄混淆,他是整个数据块,而不是简单的行记录,由于Oracle本身不知道在拷贝时那些块可能出现热备,所以只要是BACKUP期间有DML的块(只有第一次是全redo镜像),就按照上面的情况处理,所以如果在backup期间运行大量的批处理程序,日志信息会集聚增多

还有就是为什么alter tablespace ... begin backup要冻结文件头的SCN?

这个主要是以后数据文件做恢复的起始SCN,在BEGIN BACKUP下达后,系统要对表空间执行检查点,并将该检查点前的所有事务应用都固化到数据文件,然后冻结这个SCN,直到使用END BACKUP,使备份过程结束,再更新为新的SCN,冻结的原因是因为使用操作系统命令拷贝数据文件时,他不能保证第一个读取的块就是数据文件头,如果不冻结,则可能从备份开始,已经多次更新了文件头,而此时文件头还没有被拷贝,这样等文件头被拷贝后,他的SCN已经远远大于了数据文件中其他数据块的SCN,这样从文件头的SCN来定位恢复起点就不现实了

从上面可以看出,热备与RMAN的联机备份是存在本质区别的,RMAN是连接目标数据库,产生Oracle用户进程,调用他自身的过程包,与dbwr进行交互,使用Oracle的SGA或PGA进行备份(备份会写到SGA中形成db buffer,以db buffer 为单位而非OS buffer),这样他首先会保证每个块都是一致的,不会出现热备现象,这样就减少了REDO记录的产生,他也不需要冻结文件头SCN,因为RMAN总是能先读取数据文件头的块。

在研究Postgresql数据库配置优化的时候发现PG希望CHECKPOINT越晚越好,这一点是和其他数据库不同的。大家知道CHECKPOINT越频繁,系统故障恢复时需要RECOVER的PAGE就越少,系统恢复就越快,不过写IO就越多;而CHECKPOINT越推迟,数据库故障恢复需要RECOVER的PAGE就越多。大多数数据库的CHECKPOINT都是按照尽可能早的方式去优化的,在IO允许的情况下,尽可能早的要求脏块写入磁盘,从而避免一系列的问题。这些问题不仅仅是数据库故障时的恢复问题,还包括让脏块尽可能地少,从而应对突如其来的负载突增,让系统运行的更为平稳。大多数关系型数据库的CHECKPOINT都是5-10分钟级别的。而PostgreSQL数据库不是这样,PG总是希望CHECKPOINT越晚越好,当然也不是太晚,如果因为脏块太多从而导致了大量的BACKEND进程写脏块,那么系统性能下降还是蛮厉害的。和其他数据库不同的是,PG数据库希望CHECKPOINT产生的时间是30分钟左右而不是其他数据库的10分钟左右。这是什么原因呢?这就和我们今天要讨论的FULL_PAGE_WRITES有关了。

PG的FULL_PAGE_WRITES是一个类似于Oracle的begin backup的功能,主要的目的是为了确保OS崩溃或者存储故障时,能够不产生块断裂。因为PG数据块的块大小是8K,而一次OS IO的大小不一定是8K,比如LINUX的块大小是4k,一个磁盘的物理扇区是512字节,因此一个PG的块可能被多个IO写入,而OS故障或者存储故障会导致部分IO未能正确写盘,此时就会产生一个PG块部分被写盘了,部分没有写盘,那么安排有WAL也无法正常恢复PG数据块。ORACLE DBA都清楚块断裂带来的问题,那就是出现坏块,如果坏块出现在字典表上,还可以导致ORACLE数据库无法正常启动或者部分功能无法使用。

在MYSQL数据库上,也有类似的块断裂保护机制,那就是DOUBLE WRITE LOG(log还要吗写的不是数据块嘛),通过双写来确保数据安全,这个安全的代价也是很大的,每个数据块的写盘都需要写两次。华为的Gaussdb 100 1.0.0系列版本也采用了double write来解决这个问题,OpenGauss 2.0开始也在PG核心里引入了double write机制来替代FULL_PAGE_WRITES,从而提升数据库的性能。

那么Oracle是如何解决块断裂的问题呢?有很多网上的帖子说Oracle比较牛,是通过十分复杂的算法来解决块断裂的问题的,也有人说是oracle的REDO/UNDO可以保证不出现块断裂。实际上这些说法是不正确的。Oracle确实简单粗暴的在RDBMS层面不考虑块断裂保护的事情,把这些事情交给存储系统去做,目前的ORACLE ASM里面已经支持原子写,通过ASM的原子写机制来确保块不断裂,不过ASM原子写的保证机制是否有效,还有很多争论,在使用ASM的时候出现数据库坏块也是常有的事情,因此Oracle ASM是否真正能解决块断裂问题,从哪个版本开始解决了块断裂的问题尚未得到官方的正面认证,待老白有时间的时候去研究一下。Oracle解决块断裂的最简单方法是支持块恢复(block recovery),当块断裂发生时,通过block recovery直接从备份介质中恢复某一块来解决这个问题。你都花了几百万买Oracle了,那么把备份搞上是标配,否则遇到块断裂就算你倒霉。(没有DR 就不能保证了嘛 好像也不对把。。。。。。。)

PG的FULL_PAGE_WRITES是当某个数据块在CHECKPOINT的时候被写回数据文件后,再次被修改的时候,第一次,PG会把这个块的整块写入WAL日志,而不只是写入变更的矢量。这一的话,哪怕操作系统或者存储故障,都可以在WAL中找到这个数据块的一个一致性的副本,再从这个副本开始前滚WAL日志,实现数据恢复。

FULL_PAGE_WRITES确实可以保障数据库的安全,不过这种保障是有代价的,那就是大量的WAL的产生。我在一个测试环境下做过一个测试,关闭FULL_PAGE_WRITES后,在一个NVME SSD,WAL和CHECKPOINT相关参数已经优化过的环境下,TPMC指标提升了10%以上。如果在IO性能较差的环境中,可能这个提升还会更高。有些测试表明,关闭FULL_PAGE_WRITES的性能提升可能高达30%以上。但是为了数据库的安全,在绝大多数环境中还是建议不要关闭这个功能。

正是因为PG数据库的这个机制会导致CHECKPOINT后WAL的量会剧增,因此PG总是尽可能延缓CHECKPOINT的产生,延缓把脏块写回OS。因此CHECKPOINT的优化就十分关键,如果这方面没有优化好,那么PG的写入性能就会很差。

另外在一些情况下,如果你的OS或者文件系统支持原子写入,比如ZFS文件系统,它能够确保一个块被以原子的方式一次性写入(ZFS也是存在类似WAL的日志的),那么PG就可以关闭FULL_PAGE_WRITES了。有国外的网友测试过,使用ZFS,关闭FULL_PAGE_WRITES,PG的QPS提高了超过20%。实际上目前LINUX的EXT4文件系统也是支持日志的,如果在EXT4打开日志的情况下(mount参数里加入data=journal),我们也可以关闭FULL_PAGE_WRITES,从而获得性能的提升。

不过任何情况下,多一层安全保护总不是坏事,因此当我们的PG数据库系统还没有需要挖掘每一分性能潜力,必须通过关闭FULL_PAGE_WRITES来解决问题的时候,我们还是暂时先不要关闭这个选项。首先考虑通过优化CHECKPOINT相关的配置,把WAL放到更快的盘上来优化性能。另外要注意的是FULL_PAGE_WRITES对于数据块的修改操作影响很大,而对于只读操作的影响是有限的,在考虑是不是要关闭FULL_PAGE_WRITES的时候,也需要充分评估风险与获得的好处之间的收益率。

另外在分析FULL_PAGE_WRITES产生了多大的影响的时候,通过pg_bgwriter_stat性能视图去分析backend写脏块的比例是十分有效的,如果这个比例太高,那么很可能你的CHECKPOINT有点太慢了。不同的应用负载,CHECKPOINT的调整策略是不同的,需要根据你的系统的实际情况去做调整,而不要用僵化的思路去调整。

我对PG 15的期待最主要集中在FULL PAGE WRITE的优化上了。以前我也多次写文章研究过PG的WAL产生以及CHECKPOINT对高负载应用写入性能的影响。其实这些性能影响大部分来自于FULL PAGE WRITE。因为当某个脏块在CHECKPOINT完成后的第一次被写入的时候,都需要做一次FULL PAGE WRITE,以避免数据库需要恢复时出现块断裂的情况。而FULL PAGE WRITE大规模产生的时候,因为WAL的写入量大大增加,就会导致高并发写应用的性能问题。实际上,当我们在做pg_basebackup等在线备份操作时(oracle中 begin backup),也是会引发大量的FULL PAGE WRITE的。缓解这个问题的方法是加大CHECKPOINT的延时,让两次CHECPOINT的间隔更长一些,这样就会大量减少FULL PAGE WRITE。

加大CHEKPOINT的延时也是一个双刃剑,因为这会让数据库在恢复时需要RECOVER更多的页,同时也会在一个CHECKPOINT时集中写入大量的数据块。在瞬时对IO产生更大的影响。

在PG中关闭FULL PAGE WRITE也是一个选项,只不过需要找到支持PG关闭FULL PAGE WRITE的文件系统或者存储系统。我们以前曾经在ZFS上尝试过关闭FULL PAGE WRITE,效果是相当不错的。只不过在我们的大多数PG运行环境中并没有使用ZFS,因此对于PG 15在FULL PAGE WRITE上的优化,我们还是很期待的。

我曾经也考虑过通过WAL压缩来缓解FULL PAGE WRITE的问题,在PG 12上我们做过一个测试,打开WAL压缩。只不过打开WAL压缩后,PG数据块的高并发写入性能并未提升,反而略有下降。能够从以前的PG 12的WAL压缩中获得性能提升的场景十分有限。

PG 15的WAL压缩有了一个十分令人兴奋的改进,除了支持PGLZ外,WAL压缩还支持LZ4和ZSTANDARD两种压缩算法。

从官方的对比来看,ZSTD是一种平衡了压缩解压吞吐量与压缩比的十分优秀的压缩算法,而LZ在压缩比上虽然不如ZSTD,但是在压缩吞吐量上有着特别优秀的表现。这两种压缩算法的引入,必定会大大改善WAL压缩的性能。

因为时间关系,我们目前还没有全面开展PG 15的测试,我想还是等正式版出来后再做完整的测试。不过从目前国外一些朋友对PG 15的测试来看,还是十分令人期待的。我们根据Mark Callaghan的测试用例来看一看效果吧。

和我们的预期相同,使用以前的pglz,对于仅有PK的场景影响并不大,对于PG来说,第一次数据写入,都是创建的新块,不同的算法之间的影响还不算太大。但是对于其他场景影响较大。而使用LZ4的效果极佳,装载性能有了明显的提升。zstd在有多个索引时的数据装载表现出了较好的性能。

不过从上面的测试来看,还是比较简单的,因此这个测试结果页并不说明太多的问题。不过有一点是可以确定的,那就是LZ4和ZSTD的引入,会大大优化WAL 压缩的性能,从而缓解FULL PAGE WRITE的问题,也可以大大减轻DBA优化CHECKPOINT的工作。CHECKPOINT的优化可能会在大多数场景下变得更简单了,我们只要根据自己的场景需求,选择设置WAL压缩参数就可以了。

挺巧的是,在PG 15里,增加了一个新的权限:pg_checkpointer。以往checkpint命令只能由超级用户来执行。而PG 15里,只要是授予这个权限的用户,都可以在自己的应用里执行checkpoint命令了。将checkpoint命令的权限下放,是PG 15细粒度权限控制优化的一部分。不过既然敢下放权限,那就说明checkpoint带来的副作用已经可控了。随着现代硬件的发展,IO问题已经得到了大大地缓解,同时因为WAL压缩的优化,也为checkpoint权限下放提供了有力的支撑。

不过业务场景十分复杂,这个权限还是不要随便授予,因为目前来看,我还没有想清楚,这个权限下放的真实好处在什么地方,什么场景下,应用需要自己去控制checkpoint。如果有一天,DBA发现一个挠头的IO性能问题,是因为某个应用系统频繁执行checkpoint引发的,那就麻烦大了。对于开发人员来说,让每条记录尽快写盘肯定是件喜闻乐见的事情,而对于DBA来说,那可能是个灾难。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值