天天看点

PostgreSQL 可靠性分析 - 关于redo block原子写

digoal

2016-10-11

postgresql , redo , redo block 原子写 , 可靠性分析

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。

PostgreSQL 可靠性分析 - 关于redo block原子写

(ps: 某些企业级ssd可以通过电容残余的电量,将disk cache里的数据持久化下来,但是请不要相信所有磁盘都有这个功能)

2. 当开启了易失缓存时,如果写数据的块大小小于或等于磁盘原子写的大小(即"原子写"),掉电时也可能出现partial write。

对于mysql来说,redo的写为512字节的,其中包含12个字节的头信息,4个字节的校验信息。

这个怎么理解呢,为什么没有对齐则可能出现。

PostgreSQL 可靠性分析 - 关于redo block原子写

1. 前面提到了,如果没有对齐,并且开启了易失缓存,原子写是没有用的,同样会出现partial write。

2. 如果没有对齐,会造成写放大,本来写512字节的,磁盘上会造成写1024字节(将两个扇区数据读出来再与要写的数据合并, 分成两个扇区回写)。

1. 开启易失缓存时,原子写一样会丢失易失缓存中的数据。

2. 当未对齐时,原子写并不是真的原子写。

数据库只靠redo的原子写,如果不考虑以上两个因素,起不到保证数据可靠性和一致性的作用。

1. shared buffer 中的dirty page在write前,必须要保证对应的redo已经持久化(指已经落到非易失存储介质)。

2. 在检查点后出现的脏页,必须要在redo中写dirty page的full page。

这2条保证的是数据文件的一致性。

3. 在不考虑standby的情况下,当设置为同步提交的事务在事务提交时,必须等待事务产生的redo已持久化才返回(指已经落到非易失存储介质)。

参考

<a href="https://github.com/digoal/blog/blob/master/201610/20161006_02.md">《postgresql 9.6 同步多副本 与 remote_apply事务同步级别》</a>

4. 当设置为异步提交的事务在事务提交时,不需要等待事务产生的redo持久化。

由于有第一条的保护,所以即使使用异步事务,丢失redo buffer中的数据后,也不会出现不一致(比如一半提交,一半未提交)的情况,仅仅丢失redo buffer中未提交的事务而已。

一致性由postgresql mvcc的机制来保证,不会读到脏数据。

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已持久化。

所以用户收到反馈的事务,一定是持久化的,不可能存在partial write。

而没有收到反馈或未结束的事务,才有可能包含partial write,那么问题就简化了:

这些没有收到反馈或未结束的事务产生的redo 出现partial write会不会导致数据不一致?

回答是不会,参考前面 "postgresql如何保证数据库可靠性",mvcc机制可以保证这些 。

数据库参数

产生测试数据

模拟压力测试

观测到产生了一些xlog,约200秒后,测试过程中强制停库,下次启动会进入恢复状态

记录接下来要纂改的redo文件以及之前的文件最后的内容

纂改的前一个文件的末尾的一些内容,用于判断已持久化的记录

能看到几笔commit rec就行了

被纂改的文件的头部的内容,用于判断未持久化的记录

这里显示的都是将要纂改掉,对pg来说就是未持久化的事务,数据库恢复后是不会显示的.

纂改redo

启动数据库,进入恢复状态,当读到checksum不一致的block,停止继续往前,也就是说数据库恢复到这里截至。

未恢复的事务造成的变更,对用户不可见。

验证

通过检验。

如果每产生一笔redo都要fsync,性能就差了,所以fsync实际上是有调度的。

redo buffer的作用就是减少fsync的次数。

1. 当wal writer sleep超过设置的sleep时间(通常设置为10毫秒)时,触发fsync,将redo buffer中已写完整的block持久化到redo file。

2. 当wal writer write(异步写)的字节数超过配置的阈值(wal_writer_flush_after)时,触发fsync,将redo buffer中已写完整的block持久化到redo file。

3. 当事务结束时,检查wal write全局变量,lsn是否已flush,如果没有落盘,则触发fsync。

4. 第三种情况,如果开启了分组提交,则多个正在提交的事务只会请求一次fsync。

5. 当redo 日志文件发生切换时,会触发fsync,确保文件持久化。

src/backend/postmaster/walwriter.c

src/backend/access/transam/xlog.c

如果要深入了解postgresql redo的内部机制,可以参考以上文档以及源码。

PostgreSQL 可靠性分析 - 关于redo block原子写

<a href="http://info.flagcounter.com/h9v1">count</a>