1.WAL简介
WAL 全称 write ahead log,是PostgreSQL中的在线redo日志。一般的数据库系统为保障数据的安全性,数据文件的改变必须先写入日志,也就是说日志记录刷新到永久存储上后,才能被提交。这样,在数据库宕机时,没有“落盘” 的数据,可以根据 WAL 的记录进行重做。根用WAL日志还能提高事物的效率,因为避免了每次提交时的随机读写,而只是顺序的刷新WAL记录到WAL段。
1.1.WAL的工作原理
- 事物开始后,提交前:先将变更记录到WAL缓冲,然后再将更新后的数据写入到数据页缓冲
- 提交时:WAL缓冲立即刷新到WAL段,后续根据不同场景连续地将数据页缓冲写入到数据文件
- checkpoint时:将所有的数据页缓冲写到数据文件。
2.WAL segment
存储在$PGDATA/pg_wal目录(9.6版本之前在pg_xlog),信息如下:
[root@test pg_wal]# pwd
/data/pgdata/pg_wal
[root@test pg_wal]# ll
total 16384
-rw------- 1 pg14 pg14 16777216 Mar 15 13:07 000000010000000000000001
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
[root@test pg_wal]#
参数wal_segment_size控制每个wal文件的大小,默认是16M,max_wal_size控制所有wal文件大小的总和,默认为1G,超过这个大小就会覆盖(重用最早的WAL segment),以默认值为例,允许最多有64个wal segment,超过64个就会重用之前的wal segment。在11版本之前wal_segment_size只能在编译时指定,指定后不能变更,在11版本之后,wal_segment_size在初始化时(initdb)指定,并且只能使用pg_resetwal插件变更。
2.1 wal segment 命名格式
由24位十六进制数组成:前8位 代表 timeline,中间8位代表logid,最后8位代表segment id。
2.2 wal segment 内部结构
一个16M的segment由 2048个8k的block(块,也叫做页)组成。
每个个block包含一个blcok header(块头)和若干个 wal record,wal record就存放数据变更记录。
第一个block header由xLogLongPageHeaderData定义数据结构,其他block header由xLogPageHeaderData定义数据结构,这两个都在/soft/postgresql-14.6/src/include/access/xlog_internal.h中。
wal recored 由 header和data两部分,header由xLogRecored定义数据结构,data可能包含多种数据结构,xLogRecored在/soft/postgresql-14.6/src/include/access/xlog_internal.h中(9.5版本前在xlog.h)。
3 WAL相关参数
postgres=# select name,setting,context from pg_settings where name like '%wal%';
name | setting | context
-------------------------------+-----------+------------
max_slot_wal_keep_size | -1 | sighup
max_wal_senders | 10 | postmaster
max_wal_size | 1024 | sighup
min_wal_size | 80 | sighup
track_wal_io_timing | off | superuser
wal_block_size | 8192 | internal
wal_buffers | 256 | postmaster
wal_compression | off | superuser
wal_consistency_checking | | superuser
wal_init_zero | on | superuser
wal_keep_size | 0 | sighup
wal_level | replica | postmaster
wal_log_hints | off | postmaster
wal_receiver_create_temp_slot | off | sighup
wal_receiver_status_interval | 10 | sighup
wal_receiver_timeout | 60000 | sighup
wal_recycle | on | superuser
wal_retrieve_retry_interval | 5000 | sighup
wal_segment_size | 16777216 | internal
wal_sender_timeout | 60000 | user
wal_skip_threshold | 2048 | user
wal_sync_method | fdatasync | sighup
wal_writer_delay | 200 | sighup
wal_writer_flush_after | 128 | sighup
(24 rows)
常用的有max_wal_size 、wal_level 、wal_buffers、wal_segment_size。当不是PostgreSQL 流复制时还需要配置max_wal_senders、wal_keep_size、wal_sender_timeout。
- max_wal_size:在自动 WAL 检查点之间允许 WAL 增长到的最大尺寸。这是一个软限制,在特殊的情况下 WAL 尺寸可能会超过
max_wal_size
, 例如在重度负荷下、archive_command
失败或者高的wal_keep_size
设置。 如果指定值时没有单位,则以兆字节为单位。默认为 1 GB。增加这个参数可能导致崩溃恢复所需的时间。 这个参数只能在postgresql.conf
或者服务器命令行中设置。 - min_wal_size:只要 WAL 磁盘用量保持在这个设置之下,在检查点时旧的 WAL 文件总是 被回收以便未来使用,而不是直接被删除。这可以被用来确保有足够的 WAL 空间被保留来应付 WAL 使用的高峰,例如运行大型的批处理任务。 如果指定值时没有单位,则以兆字节为单位。默认是 80 MB。这个参数只能在
postgresql.conf
或者服务器命令行中设置。 - wal_level:
wal_level
决定多少信息写入到 WAL 中。默认值是replica
,它会写入足够的数据以支持WAL归档和复制,包括在后备服务器上运行只读查询。minimal
会去掉除从崩溃或者立即关机中进行恢复所需的信息之外的所有记录。最后,logical
会增加支持逻辑解码所需的信息。每个层次包括所有更低层次记录的信息。这个参数只能在服务器启动时设置。要启用WAL归档就必须使用replica或logical级别。 - wal_buffers:wal缓冲大大小,默认值 -1 选择等于shared_buffer的 1/32 的尺寸(大约3%),但是不小于
64kB
也不大于 WAL 段的尺寸(通常为)。如果自动的选择太大或太小可以手工设置该值,但是任何小于32kB
的正值都将被当作32kB
。 如果指定值时没有单位,则以WAL块作为单位,即为XLOG_BLCKSZ
字节,通常为8kB。这个参数只能在服务器启动时设置。 - wal_segment_size:单个WAL文件的大小,默认为16MB。
4.WAL实验
#为了尽快覆盖,我们将max_wal_size设置为64M,即最多允许4个wal segment
postgres=# show max_wal_size;
max_wal_size
--------------
64MB
(1 row)
postgres=# show wal_segment_size;
wal_segment_size
------------------
16MB
(1 row)
#查看pg_wal目录下wal_segment 只有1个
[root@test access]# cd /data/pgdata/pg_wal/
[root@test pg_wal]# ll
total 16384
-rw------- 1 pg14 pg14 16777216 Mar 16 11:55 000000010000000000000001
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
#执行wal切换
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/1AB4300
(1 row)
#目录下多个一个,segment_id(后8位) 加1
[root@test pg_wal]# ll
total 32768
-rw------- 1 pg14 pg14 16777216 Mar 16 11:56 000000010000000000000001
-rw------- 1 pg14 pg14 16777216 Mar 16 11:56 000000010000000000000002
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
#又切换了2次,
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/2000078
(1 row)
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/30000F0
#查看WAL目录
[root@test pg_wal]# ll
total 49152
-rw------- 1 pg14 pg14 16777216 Mar 16 11:59 000000010000000000000003
-rw------- 1 pg14 pg14 16777216 Mar 16 11:59 000000010000000000000004
-rw------- 1 pg14 pg14 16777216 Mar 16 11:59 000000010000000000000005
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
只有3个? 显然4*16M触发了max_wal_size,执行了清理。
#再切换一次
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/4000078
(1 row)
[root@test pg_wal]# ll
total 49152
-rw------- 1 pg14 pg14 16777216 Mar 16 12:02 000000010000000000000005
-rw------- 1 pg14 pg14 16777216 Mar 16 11:59 000000010000000000000006
-rw------- 1 pg14 pg14 16777216 Mar 16 12:02 000000010000000000000007
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
清理了3和4,保留了5,多给了6和7
postgres=# select pg_walfile_name(pg_current_wal_lsn());
pg_walfile_name
--------------------------
000000010000000000000005
当前使用的wal 是5
一次清理2个是固定的吗?显然不是的,每当检查点启动时,PG都会估计并准备下一个检查点周期所需的wal segment数量,这个估计基于前一个检查点周期中消耗的文件数据量,这个值应该在min_wal_size和max_wal_size之间。如果我们调大max_wal_size。那么能保留的wal segment就会变多,当达到阈值需要清理时,一次清理的也会变多下面验证
postgres=# alter system set max_wal_size='129MB';
ALTER SYSTEM
postgres=# exit
[pg14@test ~]$ pg_ctl stop -D $PGDATA -l /tmp/logfile
waiting for server to shut down.... done
server stopped
[pg14@test ~]$ pg_ctl start -D $PGDATA -l /tmp/logfile
waiting for server to start.... done
server started
[pg14@test ~]$ psql -Upostgres -dpostgres
psql (14.6)
Type "help" for help.
postgres=# show max_wal_size;
max_wal_size
--------------
129MB
postgres=# select pg_switch_wal(); --5 ->6
pg_switch_wal
---------------
0/50001D8
(1 row)
postgres=# select pg_switch_wal(); --6 ->7
pg_switch_wal
---------------
0/6000078
(1 row)
postgres=# select pg_switch_wal(); --7 ->8
pg_switch_wal
---------------
0/7000000
(1 row)
[root@test pg_wal]# ll
total 65536
-rw------- 1 pg14 pg14 16777216 Mar 16 12:17 000000010000000000000005
-rw------- 1 pg14 pg14 16777216 Mar 16 12:17 000000010000000000000006
-rw------- 1 pg14 pg14 16777216 Mar 16 12:18 000000010000000000000007
-rw------- 1 pg14 pg14 16777216 Mar 16 12:18 000000010000000000000008
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
postgres=# select pg_switch_wal(); --8->9
pg_switch_wal
---------------
0/7000078
(1 row)
[root@test pg_wal]# ll
total 81920
-rw------- 1 pg14 pg14 16777216 Mar 16 12:18 000000010000000000000009
-rw------- 1 pg14 pg14 16777216 Mar 16 12:17 00000001000000000000000A
-rw------- 1 pg14 pg14 16777216 Mar 16 12:17 00000001000000000000000B
-rw------- 1 pg14 pg14 16777216 Mar 16 12:18 00000001000000000000000C
-rw------- 1 pg14 pg14 16777216 Mar 16 12:18 00000001000000000000000D
drwx------ 2 pg14 pg14 6 Mar 15 10:14 archive_status
这次直接清理了4个。
wal segment的数量还受wal_keep_size 控制,针对wal segment的数量官网文档如下解释:
pg_wal
目录中的 WAL 段文件数量取决于min_wal_size
、max_wal_size
以及在之前的检查点周期中产生的 WAL 数量。当旧的日志段文件不再被需要时,它们将被移除或者被再利用(也就是被重命名变成数列中未来的段)。如果由于日志输出率的短期峰值导致超过max_wal_size
,不需要的段文件将被移除直到系统回到这个限制以下。低于该限制时,系统会再利用足够的 WAL 文件来覆盖直到下一个检查点之前的需要。这种需要是基于之前的检查点周期中使用的 WAL 文件数量的移动平均数估算出来的。如果实际用量超过估计值,移动平均数会立即增加,因此它能在一定程度上适应峰值用量而不是平均用量。min_wal_size
对回收给未来使用的 WAL 文件的量设置了一个最小值,这个参数指定数量的 WAL 将总是被回收给未来使用,即便系统很闲并且 WAL 用量估计建议只需要一点点 WAL 时也是如此。独立于
max_wal_size
之外,始终保留最新的 wal_keep_size 兆字节的 WAL 文件和一个额外的 WAL 文件。还有,如果使用了 WAL 归档,旧的段在被归档之前不能被移除或者再利用。如果 WAL 归档无法跟上产生 WAL 的步伐,或者如果archive_command
重复失败,旧的 WAL 文件将累积在pg_wal
中,直到该情况被解决。一个使用了复制槽的较慢或者失败的后备服务器也会带来同样的效果