什么是 WAL
WAL 全称是 write ahead log,是数据库中的事务日志。跟 mysql 的 binlog 日志和 oracle 的 redo 日志是一样的,是用来保证数据的持久性和恢复。
简单来说就是在PostgreSQL数据库中要对数据文件进行修改时必须先写入WAL日志信息,即当WAL日志记录完成了持久化,刷新到磁盘永久储存之后才能更改数据文件。
根据这个原则就不需要在每次提交事务的时候都刷新数据到磁盘。因为当数据库出现宕机发生数据丢失时,可以重新执行WAL日志来达到恢复数据库的目的。因此WAL日志也可以叫做redo重做日志,因为任何没有写到数据文件上的改动都可以根据日志记录进行重做。
默认路径在$PGDATA 的子目录pg_wal 下,这里会有一个误区,大家可能会觉得该目录下编号最大的日志名是正在使用的 wal 日志,其实不然,postgreSQL 会估计并准备下一个检査点周期所需的WAL段文件的数量,会创建多个 wal 文件,我们可通过select pg_walfile_name(pg_current_wal_lsn());来查询当前 PG 使用的 wal 文件是哪个。
WAL段文件大小(默认为 16MB)
在 PostgreSQL 10.X 之前的版本,该参数只能在编译 PostgreSQL 时设置,无法在数据初始化后更改。
在 PostgreSQL 10.X 之后的版本中,可以通过 initdb 或 pg_resetwal 工具在数据库初始化或者重置该参数。
initdb --wal-segsize = 32
pg_resetwal --wal-segsize=32 -D /data/pgsql/data/
WAL 文件命名规则
AL段文件名称是由24个十六进制数组成。可分解成三组8位十六进制数。
命名规则用公式表示如下:
WAL段文件名=timelineId + (uint32)(LSN-1)/(16M*256) + (uint32)(LSN-1)/16M %256
LSN:Log sequence number
16M:数据库初始化时段文件默认大小16M,与参数--wal-segsize指定,取值范围1-1024M
256:2^32/(16*1024*1024)
这三组十六进制数意义如下:
00000001 00000004 000000A4
-------- -------- --------
时间线标识 LogId LogSeg
时间线标识:timelineId,是以1起始递增的正整数
LogId:逻辑日志文件号,是以1起始递增的正整数
LogSeg:段文件号,是以1起始递增的正整数
根据上面公式,可知当WAL文件使用默认16M大小时,Log十六进制数前6位,也就是二进制的24位。
这个24位二进制数,又可以细分为:13位表示块号,11位表示块内偏移量。
块默认8K。
WAL文件中包含数据块/页个数:
16M/8K=2048
或
2^13=2048
数据块/页中可含有偏移量:
2^11=2048
WAL 文件内容
较难以后再补充
WAL 文件的自管理
如果你有学过 Oracle,就知道 Oracle 的 redo 日志有三个,当 A 写满之后就会切换 B,当 B 写满后就切换 C,C 写满之后就回到 A。回到正题,wal 日志也是如此,但是它不止有三个文件,我们查看 pg_wal目录,会发现该目录下有很多 wal 文件,那是不是意味着它会一直增长下去呢,别担心,pg_wal 目录的大小由 max_wal_size 决定,默认大小是 1GB。
默认情况下最大文件数 ≈ max_wal_size / wal_segment_size ≈ 1024 / 16 = 64个
超过了这个限制,那么 PostgreSQL 就会删除不必要的 wal 日志,什么叫作不必要日志?这个则是根据检查点的位置决定的,如果当前检查点记录的位置是 A,那么在 wal 日志中 A 之前的日志是不是就没用了,因为这部分数据已经持久化到磁盘上了,只需要保留 A 之后的日志内容就行了。
默认情况下,数据库将WAL记录写入存储在pg_wal子日录中的一个WAL段文件中。当段文件写满后,就需要切换到下一个,以继续存储wal更改。出现以下情况时,段文件会发生切换
wal段已经被填满
调用函数pg_switch_wal()
启用了归档(archive_mode),且已经超过archive_timeout配置的时间
调用在线备份
这四点必须要牢记起来。
PostgreSQL 无法像 Oracle 一样监控切换频率,日志切换频率是数据库性能监控的重要指标,如果某段时间切换很频繁,可能这段时间有批量导入。有个比较笨的方法就是查看每个日志最后写入时间来判断切换频率。
WAL 日志归档
上面我们有说到,wal 日志目录大小是有限制的。当超过max_wal_size 时候,旧的 WAL 日志会被清除,当如果有天我们需要用到这部分日志,却找不到数据那么就没办法进行恢复了。
在生产环境中,为了保证数据的高可用性,通常需要对日志进行归档。所谓的归档就是把旧的日志移动到指定的归档目录下,即使这部分日志被删除,我们还可以通过归档进行恢复。
配置归档需要开启以下参数:
archive_mode = on
archive_command = 'cp %p /data/pgsql/archives/%f'
其中%p 指的是复制的 WAL 段,%f 是存档日志。注意阿:归档路径 postgres 用户要有权限进行访问。
当我们配置完成之后需要重启一下,然后我们来做个实验看看该配置有没有生效:
create table t1(a int);
insert into t1 values (generate_series(1,10000000));
[root@192 archives]# pwd
/data/pgsql/archives
[root@192 archives]# ls
00000001000000010000005A 00000001000000010000005E 000000010000000100000062 000000010000000100000066 00000001000000010000006A
00000001000000010000005B 00000001000000010000005F 000000010000000100000063 000000010000000100000067 00000001000000010000006B
00000001000000010000005C 000000010000000100000060 000000010000000100000064 000000010000000100000068 00000001000000010000006C
00000001000000010000005D 000000010000000100000061 000000010000000100000065 000000010000000100000069
可以看到归档路径下有文件了,这说明我们配置生效了。
WAL 工作流程
wal 工作流程简单概括就是四个字“先记后写”。
当数据库中数据发生变更时:
先要将变更后内容计入wal buffer中,再将变更后的数据写入data buffer。这个时候 wal buffer 还不会写到 wal 日志里。wal buffer 写入 wal 日志由以下条件决定:
数据 commit 时, wal buffer中数据刷新到 wal 日志上。也可以设置不同步,由synchronous_commit 参数决定。
checkpoint发生时:先将所有的 wal buffer 刷新到 wal 日志上,再将所有data buffer刷新到磁盘。
wal_writer_delay:WalWriter进程的写入间隔。默认值是200毫秒。如果时间过长,可能会导致WAL缓冲区内存不足;如果时间太短,会导致WAL不断写入,增加磁盘I/O负担。
wal_writer_flush_after:当脏数据超过这个阈值时,它将被刷新到磁盘,默认是 1MB。
commit_delay + commit_siblings:延迟提交以合并多个事务的WAL写入
那为什么不马上写入呢?原因很简单,简单例子,当你在淘宝下单买了一件衣服,那么快递公司会马上派车把你送你这一单吗?显然不可能,这样做的成本太高了,他们会积累到装满一辆车才会开始送。
不管是 wal buffer 刷新到磁盘还是 data buffer 刷新到磁盘上,原理都是一样的。
wal buffer 在 9.6 版本之前默认是 64KB,在 9.6 版本之后参数 wal_buffers 为-1,表示自动计算。等于shared_buffers的 1/32 的大小(大约3%),通常是不小于64kB也不大于 WAL 段的大小。
如果自动计算太大或太小可以手工设置该值,但是任何小于32kB的正值都将被当作32kB。 如果指定值时没有单位,则以WAL块作为单位,即为 XLOG_BLCKSZ 字节,通常为8kB。这个参数只能在服务器启动时设置。
影响 WAL 大小因素
有时会发现wal大量占用磁盘空间,已经超过了max_wal_size,一直没有回收到此限制以下,甚至会一直增大。可考虑以下影响wal日志大小的因素。
写放大
全页写,大量DML操作
解决方法:增加checkpoint间隔,减少全页写次数,减少dmI操作,使用wal压缩
异常增长
不管怎样,max_wal_size从来不是一个硬限制,因此需要考虑足够的空间存放段文件。有时WAL异常增长超过了max_wal_size,执行检查点后,使用量未见降低,需要排查以下因素。
独立于max_wal_size之外,wal_keep_size(MB)+1个最近的 WAL文件将总是被保留。(pg13之前的版本是wal_keep_segments)。
启用了WAL归档,旧的段在被归档之前不能被移除或者再利用。即归档失败
启用了复制槽功能,一个使用了复制槽的较慢或者失败的后备服务器也会导致WAL不能被删除或重用
checkpoing末完成
长事务未提交。
standby备库启用了hot_standby_feedback。大批量数据插入,会造成一个瞬时高峰,wal占用空间大小,总是以高峰期值估算。
注意,wal占用空间增加,会导致备份占用空间增大。
评论