emmm。。。WAL NUTS
1. WAL里有什么1.1 WAL在事务中的位置1.2 WAL记录1.3 事务的提交1.3.1 ASync Commit2. WAL能干什么2.1 普通恢复2.2 有整页镜像的恢复3. 怎么管理WAL3.1 事务日志的分段存储3.2 Wal Writer进程3.3 Checkpointer进程时间到了wal日志数量到了主动执行3.4 Wal段文件管理3.4.1 何时切换wal段文件3.4.2 如何安全地删除wal3.4.3 从归档wal中恢复
DB所处的运行环境有随时崩溃的可能,DB上的任务就是一个个的事务;就单个事务而言,我们需要知道事务是成功还是失败的(atomic in acid),另外事务一旦提交成功后,如何要确保事务进行的修改不会丢失(durability in acid)呢?emmm,DB就是通过记日志的方式确保这两个问题。日志呢,分为两种REDO和UNDO,在PostgreSQL中是一种REDO日志,叫做WAL,即Write Ahead Log。
1. WAL里有什么
wal是一种redo日志,记录了数据的历史操作,但不只是command log(MySQL似乎是这么搞的);
Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;
1.1 WAL在事务中的位置
Write Ahead Log,顾名思义就是在提前写的日志;当执行一个事务时:
首先在CLOG中记录
in progress
状态;而后执行相应的SQL,但每条DML操作之前会在WAL缓存区中先写一条修改的记录,并更新数据页的lsn;
提交事务(提交的操作见第三节)
将CLOG中的事务状态改为COMMITED
CLOG
Commit Log,记录事务提交状态;在pg_xact中,同时在shared buffer中,有相应的事务状态缓存区。
1.2 WAL记录
通过pg_waldump可以看到wal中的信息,每一条记录对应一个rmgr(resourece manager)和相应resource上的操作;比如通过下面简单的命令,找到了一个COMMIT的记录,表示tid=25413的事务成功提交了。
$ pg_waldump --start 2/783D9D50 000000010000000200000078 -r Transaction
rmgr: Transaction
len (rec/tot): 46/ 46,
tx: 25413,
lsn: 2/783D9D50,
prev 2/783D9D18,
desc: COMMIT 2018-08-18 15:59:46.682666 CST复制
当DB崩溃,重启DB的时候,会进行数据库恢复;此时有一个问题:从哪个点开始恢复的? 即,REDOpoint——最近的checkpoint开始的时候写入checkpoint record的位置:
$ pg_waldump --start 2/783D9D50 000000010000000200000078 -r XLOG
rmgr: XLOG
len (rec/tot): 106/ 106,
tx: 0,
lsn: 2/78499528, prev 2/784994F0,
desc: CHECKPOINT_ONLINE redo 2/784994F0;
tli 1; prev tli 1; fpw true; xid 0:25416; oid 33250; multi 2; offset 3; oldest xid 548 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 25416; online复制
在源码的backend/access/rmgrdesc下,可以看到各个资源管理器的描述文件,这些RMGR代表着PostgreSQL里每个事务对各个存储的对象操作;比如Heap就是堆表的操作,以及各种索引等。
$ pg_waldump --rmgr=list
XLOG
Standby
Heap2
Heap
Btree
Hash
Gin
Gist
Sequence
...复制
1.3 事务的提交
1.3.1 ASync Commit
理论上,每次commit的时候都是需要等wal record buffer刷盘后,才返回成功。其实当commit时,具体的行为有很多种,由synchronous_commit参数决定,
当off时,此时就是Async Commit,返回commit成功后,最多等待3*wal_writer_delay的时间,强制刷盘;
如果synchronous_standby_names不是空的话,
默认是
on
,那么master会等待sync standby的确认收到commit record后,返回成功,此时数据不会丢失;另外还有
remote_apply
,确认sync standby也完成了这个事务,此时修改在备库也是可见的。同理
remote_write
, 就是确保备库上也刷盘了。而
local
就只确保本地刷盘即可,不管replication。如果synchronous_standby_names是空的话
就只管local了。
关闭synchronous_commit,不会导致数据不一致性,顶多会丢掉几个commit的事务,但是数据库中的数据是一致的。而关闭fsync,数据的落盘就没有保证了,如果你关了fsync,那么连full_page_write也一起关了得了。
所以想提高性能,可以考虑关闭synchronous_commit(最好还是别关)。
值得注意的是,synchronous_commit可以在任何地方设置,甚至可以同一个应用控制不同事务的提交级别。
2. WAL能干什么
PostgreSQL的WAL是保证持久化到磁盘中的,并有变更的详细描述,所以在crash之后,可以根据WAL进行恢复。一般流程如下:
当PostgreSQL启动的时候,检查pg_control文件中的state字段,如果是
in production
,表示非正常结束; PostgreSQL进入恢复默认;从pg_control找到最新的checkpoint,从WAL相应的checkpoint位置开始恢复,如果该checkpoint损坏,从pg_control读取prior checkpoint(PG11中将不会存储这个信息);
开始恢复;
2.1 普通恢复
PostgreSQL的每个页头部上有最后更新的lsn;
PostgreSQL比较WAL记录的lsn和数据页的lsn:如果WAL记录的lsn大,那么将这个record重放(redo),并修改页的lsn;否则,不做操作
重放的时候按照rmgr对应的资源管理器('Standby,Transaction,Sequence,Btree,Heap')进行对应的操作;比如下例,对Heap资源进行恢复操作,就是在关系表1663/13451/16498的相应偏移上插入一条记录。
rmgr: Heap
len (rec/tot): 175/ 175,
tx: 24388,
lsn: 2/5B002070,
prev 2/5B0013B8,
desc: INSERT off 46,
blkref #0: rel 1663/13451/16498 blk 42复制
2.2 有整页镜像的恢复
打开full_page_write选项后,在checkpoint后,对页面的第一次修改会将整个页复制到wal记录中,那么恢复的时候,当记录是整页镜像时,直接用整页镜像覆盖这个page,并更新这个page的lsn;
这个整页镜像叫做backup block;non-backup block中的redo操作不是幂等的,所以要按照xlog的lsn大小,顺序恢复;backup block直接覆盖即可。
3. 怎么管理WAL
3.1 事务日志的分段存储
事务日志可以非常大,但是如果在一个文件里不方便管理,我们将事务日志切分为16Mb的segment;每个segment是一个24位的16进制数字,前八位是timelineid,中间8位和最后8位可以看做是 一个256进制的两位数:当最后8位满0xFF进1的时候,中间8位+1;以此类推
通过pg_walfile_name函数,可以传入一个lsn值,得到该lsn对应的record在哪个wal中;
# select pg_walfile_name('2/783D9D50');
-[ RECORD 1 ]---+-------------------------
pg_walfile_name | 000000010000000200000078复制
一个16Mb的wal段,内部切分为8k大小的页;第一个页有一个整段的头部数据,其他每个页也有相应页的定义;
3.2 Wal Writer进程
这个服务进程间歇的运行,将wal buffer刷新到磁盘上,主要是防止同时提交太多,导致磁盘IO过载;
3.3 Checkpointer进程
当出现以下三种情况下时,checkpointer进程启动,进行CHECKPOINT操作:
时间到了
checkpoint_timeout
: 距离上次checkpoint过去了这些时间,默认5分钟
wal日志数量到了
9.4之前的配置了checkpoint_segment参数,默认是3,消费了这些wal segment,就会触发checkpoint
9.5之后,总的walfile超过了
max_wal_size
;默认64个file 1GB
主动执行
PostgreSQL在smart或者fast模式stop,start_basebackup
superuser,手动执行,checkpoint
3.4 Wal段文件管理
3.4.1 何时切换wal段文件
只要每次产生了一个新的wal,就执行archive_commond:
满了
pg_switch_xlog
archive_mode = on且archive_timeout
3.4.2 如何安全地删除wal
如果想要删除wal日志,要么让pg在CHECKPOINT的时候自己删除,或者使用pg_archivecleanup
。除了以下三种情况,pg会自动清除不再需要的wal日志:
archive_mond=on
,但是archive_command
failed,这样pg会一直保留wal日志,直到重试成功。wal_keep_segments
需要保留一定的数据。9.4之后,可能会因为replication slot保留;
如果都不符合上述情况,我们想要清理wal日志,可以通过执行CHECKPOINT
来清理当前不需要的wal。
在一些非寻常情况下,可能需要pg_archivecleanup
命令,比如由于wal归档失败导致的wal堆积引起的磁盘空间溢出。你可能使用这个命令来清理归档wal日志,但是永远不要手动删除wal段;
3.4.3 从归档wal中恢复
可以手动的将wal从归档复制回pg_xlog/pg_wal中,或者创建一个recovery.conf其中配置好restore_command来做这件事。