首页 > > > PostgreSQL 可靠性分析
PostgreSQL 可靠性与大多数关系数据库一样,都是通过REDO来保障的。
群里有位童鞋问了一个问题,为什么PostgreSQL的REDO块大小默认是8K的,不是512字节。
这位童鞋提问的理由是,大多数的块设备扇区大小是512字节的,512字节可以保证原子写,而如果REDO的块大于512字节,可能会出现partial write。
那么PostgreSQL的redo(wal) 块大小设置为8KB时,靠谱吗?本文将给大家分析一下。
1. 当开启了易失缓存时,如果写数据的块大小大于磁盘原子写的大小(通常为512字节),掉电则可能出现partial write。
例如disk cache,没有掉电保护,而且操作系统的fsync接口不感知disk cache,如果你调用了fsync,即使返回成功,数据其实可能还在disk cache里面。
当发生掉电时,在disk cache里的数据会丢失掉,如果程序写一个8K的数据,因为磁盘的原子写小于8K,则可能出现8K里有些写成功了,有些没有写成功,即partial write。

(ps: 某些企业级SSD可以通过电容残余的电量,将DISK CACHE里的数据持久化下来,但是请不要相信所有磁盘都有这个功能)
2. 当开启了易失缓存时,如果写数据的块大小小于或等于磁盘原子写的大小(即"原子写"),掉电时也可能出现partial write。
对于MySQL来说,REDO的写为512字节的,其中包含12个字节的头信息,4个字节的校验信息。
这个怎么理解呢,为什么没有对齐则可能出现。

1. 前面提到了,如果没有对齐,并且开启了易失缓存,原子写是没有用的,同样会出现partialwrite。
2. 如果没有对齐,会造成写放大,本来写512字节的,磁盘上会造成写1024字节(将两个扇区数据读出来再与要写的数据合并, 分成两个扇区回写)。
1. 开启易失缓存时,原子写一样会丢失易失缓存中的数据。
2. 当未对齐时,原子写并不是真的原子写。
数据库只靠REDO的原子写,如果不考虑以上两个因素,起不到保证数据可靠性和一致性的作用。
1. shared buffer 中的dirty page在write前,必须要保证对应的redo已经持久化(指已经落到非易失存储介质)。
2. 在检查点后出现的脏页,必须要在redo中写dirty page的full page。
这2条保证的是数据文件的一致性。
3. 在不考虑standby的情况下,当设置为同步提交的事务在事务提交时,必须等待事务产生的REDO已持久化才返回(指已经落到非易失存储介质)。
参考
《PostgreSQL 9.6 同步多副本与 remote_apply事务同步级别》
4. 当设置为异步提交的事务在事务提交时,不需要等待事务产生的REDO持久化。
由于有第一条的保护,所以即使使用异步事务,丢失REDO buffer中的数据后,也不会出现不一致(比如一半提交,一半未提交)的情况,仅仅丢失redo buffer中未提交的事务而已。
一致性由PostgreSQLMVCC的机制来保证,不会读到脏数据。
1. 在使用COW的文件系统(如btrfs, zfs)时,可以关闭full page write,因为这种文件系统可以保证不会出现partial write。
2. 对齐,可以避免写放大的问题。
3. 不要使用易失缓存,但是可以使用有掉电保护的易失缓存。
PostgreSQL认为系统提供的fsync调用是可靠的,即写到了持久化的存储。
如果连fsync都不可靠了,管它是不是原子写,都是不可靠的。
包括DirectIO在内(PostgreSQL支持REDO使用DirectIO),也无法感知disk cache,所以请慎重。
首先,前面已经分析了,原子写并不能抵御易失存储导致的丢数据。
1. PostgreSQL redo block是有checksum的,可以保证块的一致性,不会APPLY不一致的块。
2. 事务提交时,返回给用户前,一定会保证REDO已持久化。
所以用户收到反馈的事务,一定是持久化的,不可能存在partialwrite。
而没有收到反馈或未结束的事务,才有可能包含partialwrite,那么问题就简化了:
这些没有收到反馈或未结束的事务产生的REDO 出现partial write会不会导致数据不一致?
回答是不会,参考前面"PostgreSQL如何保证数据库可靠性",MVCC机制可以保证这些。
数据库参数
wal_level = logical 便于观察,验证
产生测试数据
pgbench -i -s 100模拟压力测试
pgbench -M prepared -n -r -P 2 -c 32 -j 32 -T 1000观测到产生了一些XLOG,约200秒后,测试过程中强制停库,下次启动会进入恢复状态
pg_ctl stop -m immediate 记录接下来要纂改的REDO文件以及之前的文件最后的内容
纂改的前一个文件的末尾的一些内容,用于判断已持久化的记录
能看到几笔commit rec就行了




被纂改的文件的头部的内容,用于判断未持久化的记录
这里显示的都是将要纂改掉,对PG来说就是未持久化的事务,数据库恢复后是不会显示的.
pg_xlogdump -b -n 20 0000000100000116000000F8 0000000100000116000000F9


纂改redo
cd $PGDATA/pg_xlog 找到最后两个xlog文件,纂改一下offset 100字节后的中间位置,模拟partial write。 dd if=/dev/zero of=./0000000100000116000000F8 bs=1 count=10000 skip=100dd if=/dev/zero of=./0000000100000116000000F9 bs=1 count=10000 skip=100启动数据库,进入恢复状态,当读到checksum不一致的block,停止继续往前,也就是说数据库恢复到这里截至。
未恢复的事务造成的变更,对用户不可见。



./configure --with-wal-blocksize=? Allowed values are 1,2,4,8,16,32,64.1. https://www.pgcon.org/2012/schedule/attachments/258_212_Internals%%20Of%%20PostgreSQL%%20Wal.pdf
如果要深入了解PostgreSQLredo的内部机制,可以参考以上文档以及源码。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69917105/viewspace-3161877/ ,如需转载,请注明出处,否则将追究法律责任。