PostgreSQL XLog 浅析
背景
XLog是PostgreSQL中的WAL,像MySQL中的RedoLog一样,提供预写日志的能力。
预写日志的主要目的包括事务中的两个概念:
- 原子性
- 持久性
同时,预写日志的写入方式大多又是追加写(顺序写),这在传统存储介质中性能远远优于随机写,同时又可以批量持久化(fsync),也间接提升了数据库的性能。
预写日志一般包含两种内容,redo 和 undo,redo一般是写入日志,undo是回滚日志;因为PostgreSQL的MVCC并没有利用回滚段,所以XLog中实际上只有redo的信息。
在PostgreSQL中关于XLog相关的变量
同时PG也支持使用pg_walinspect插件来进行DEBUG
XLog文件管理
PG的XLog一般存放在pg_xlog目录下,默认按照16MB切分为若干个文件
命名方式为24个字符,每个字符是十六进制数
前8位为时间线id,中8位为逻辑id,后8位为逻辑id下的第N个
在WAL文件写满后会切文件
双写策略
PG的数据页写入是按照page粒度的,对于WAL文件的page默认大小为8K,而操作系统的page大小默认为4K,也就是说操作系统层面只能做到4K的原子写,在异常容灾场景,很可能对于WAL文件来说,8K的page数据被截断了
为了应对这种情况,MySQL使用了innodb的double_write方案,即在bufferpool中开辟出一段空间专门用来双写的空间,我们称之为double write buffer,这段buffer有自己的刷脏策略,它在刷脏时会将数据写到ibdata中的共享表空间。于是如果crash发生在double write时,那么可以通过共享表空间中完整的page进行覆盖;如果数据页本身就是完整的,那么直接apply redolog即可恢复到一致性位点
PostgreSQL使用的是full_page_write策略,这个策略是checkpoint后的一个块第一次变脏后就要「整块」写到WAL日志中,后续修改此块则只把修改的信息写入WAL中,类似于对每个块都在初始变脏的时间点打了一个「快照」。在故障恢复时,如果有块被截断,那么则以「快照」点为基础进行恢复。
full_page_write开启是会对性能有损耗的
后来,PostgreSQL也支持了MySQL类似的方案进行双写,即开辟单独的一块buffer用于双写,它有独立的刷脏策略,同时在磁盘上也生成一个double write file充当MySQL中的共享表空间,在crash recovery时,因为double write file一定是完整的,如果有page截断则使用double write file中的数据进行覆盖补全后,apply XLog即可。
XLog的文件结构
WAL文件默认情况下16MB,其中按照8KB切分为一个Page,每个Page由 Header + Record组成
第一个Page包含XLogLongPageHeaderData,记录整个XLog文件的元信息
其他Page则以XLogPageHeaderData为头进行组织,主要记录事务日志对应的版本,时间线等信息
对于每条XLogRecord来说,也是由 Header + Data组成
XLogRecordHeader 一般记录日志的元信息,其中的xl_info每一位都代表不同信息
XLogRecordData 一般由XLogRecordBlockHeader和XLogRecordDataHeader组成
后面排列为块数据和主数据
XLogRecord的构造
XLogRecord = XLogRecordHeader + N 个 XLogRecordBlockHeader + XLogRecordDataHeader + block data + main data
其中block data通过XLogRegisBuffer方法注册到registered_buffer中
其中main data通过XLogRegisterData方法追加在XLogRecDatas链表中
同时,我们可以利用pg_xlogdump来解析一个XLog文件
其中比较重要的一些信息:
- desc : Heap 表示这是对堆表进行的操作,除了Heap之外还有Btree,Transaction等
- len : XLogRecord的长度
- xid : 事务号
- lsn : 本条记录的lsn
- pre : 上条记录的lsn
- blkref : 引用的第一个page所属堆表文件的信息,blk为块号
XLog的切换和回收
XLog的切文件时机:
- 主动发起pg_switch_xlog
- xlog写满时
- 使用archive_mode且超过archive_timeout设置值
XLog的回收时机:
理论上切换到新文件后,老的文件即可被重用或者回收,实际上还是有些不同
1 | # 自动的WAL检查点之间的日志文件段的最大数量 |
于是在正常不开启WAL归档时
通常不超过(2 + checkpoint_completion_target) * checkpoint_segments + 1
或checkpoint_segments + wal_keep_segments + 1
如果一个旧文件不再需要了会重命名然后继续覆盖使用,如果由于短期的日志输出高峰导致超过了3 * checkpoint_segments + 1
,直接删除文件
在开启归档时
只有在这个文件被归档成功后才会被删除