[WAL介绍]
Write-Ahead Logging是一种保证数据完整性的标准方法。简单地说,WAL的概念就是对数据文件的改变(包括表和索引)必须先写入日志,即日志记录刷新到永久储存之后,才能被写。遵循这个过程,就不需要在每个事务提交时都刷新数据页到磁盘,因为在宕机时可以用日志来恢复数据库:任何没有应用到数据页上的改动都可以根据日志记录重做。(即回滚恢复REDO)
对于PostgreSQL来说,未采用WAL机制之前,如果数据库崩溃,可能存在数据页不完整的风险,而WAL 在日志里保存整个数据页的内容,完美地解决了这个问题。
WAL 的中心思想是先写日志,再写数据,数据文件的修改必须发生在这些修改已经记录在日志文件中之后。
Change变更发生时:
- 先将变更后内容记入WAL Buffer
- 再将更新后的数据写入Data Buffer
Commit提交发生时:
- WAL Buffer刷新到Disk
- Data Buffer写磁盘推迟
Checkpoint检查点发生时:
- 将所有Data Buffer刷新到磁盘
[WAL优势]
当宕机发生时,
- Data Buffer的内容还没有全部写入到永久存储中,数据丢失;
- WAL Buffer的内容已写入磁盘,根据WAL日志的内容,可以恢复库丢失的内容。
在提交时,仅把WAL刷新到了磁盘,而不是Data刷新:
- 从IO次数来说,WAL刷新是少量IO,Data刷新是大量IO,WAL刷新次数少得多;
- 从IO花销来说,WAL刷新是连续IO,Data刷新是随机IO,WAL刷新花销小得多。
因此WAL机制在保证事务持久性和数据完整性的同时,成功地提升了系统性能。
[相关概念]
- Redo Log
Redo log通常称为重做日志,在写入数据文件前,每个变更都会先行写入到Redo log中。其用途和意义在于存储数据库的所有修改历史,用于数据库故障恢复(Recovery)、增量备份(Incremental Backup)、PITR(Point In Time Recovery)和复制(Replication)。
- Redo Point
REDO point是PG启动恢复的起始点,是最后一次checkpoint启动时事务日志文件的末尾亦即写入Checkpoint XLOG Record时的偏移量位置。
- WAL Segment File
PostgreSQL把事务日志文件划分为N个segment,每个segment称为WAL segment file,每个WAL segment file大小默认为16MB。
※ 此大小可以变更,但必须要重新编译。此值变更需要谨慎!
WAL segment file文件名称为24个字符,由3部分组成,每个部分是8个字符,每个字符是一个16进制值(即0~F)。每一部分的解析如下:
– 第1部分是TimeLineID,取值范围是0x00000000 -> 0xFFFFFFFF
– 第2部分是逻辑文件ID,取值范围是0x00000000 -> 0xFFFFFFFF
– 第3部分是物理文件ID,取值范围是0x00000000 -> 0x000000FF
逻辑文件ID、物理文件ID和文件大小这三部分的组合,实现了64bit的寻找空间:
– 逻辑文件ID是32bit的uint32(unsigned int 32bit)
– 物理文件ID是8bit的unit8
– 16M的文件大小是24bit的unit24
三者共同组成unit64(32+8+24),达到最大64bit的文件寻址空间。
- XLOG Record
PostgreSQL每一项变更操作都是一条XLOG Record,它存储在WAL Segment File里面的。PG会读取这些XLOG Record实现故障恢复/PITR等操作。
- WAL Buffer
WAL缓冲区,不管是WAL segment file的header还是XLOG Record都会先行写入到WAL缓冲区中,在”合适的时候”再通过WAL writer写入到WAL segment file中。如果缓冲区过小,将会消耗比较多的IO资源。如果缓冲区过大,将会有可能导致断电后数据丢失的风险。这里比较推荐使用默认值,即shared_buffers的1/32。
- LSN
LSN即日志序列号Log Sequence Number。表示XLOG record记录写入到事务日志中位置。LSN的值为无符号64位整型(uint64)。在事务日志中,LSN单调递增且唯一。
LSN由3部分组成,分别是逻辑文件ID,物理文件ID和文件内偏移。如LSN:1/4288E228,其中1为逻辑文件ID,42为物理文件ID,88E228为WAL segment file文件内偏移(注:3Bytes的寻找空间为16MB)。如下方式可以通过LSN获取对应WAL Segment Log文件:
SELECT pg_walfile_name('1/4288E228'); - Checkpointer
Checkpointer是PG中的一个后台进程,该进程周期性地执行checkpoint。当执行checkpoint时,该进程会把包含checkpoint信息的XLOG Record写入到当前的WAL segment file中,该XLOG Record记录包含了最新Redo pint的位置。
- Checkpoint
Checkpoint检查点由Checkpointer进程执行,主要的处理流程如下:
(1) 获取Redo point,构造包含此Redo point检查点信息的XLOG Record并写入到WAL segment file中;
(2) 刷新Dirty Page到磁盘上;
(3) 更新Redo point等信息到pg_control文件中; - PG Control
pg_control是磁盘上的物理文件,保存检查点的基本信息,在数据库恢复中使用,可通过命令pg_controldata查看该文件中的内容。
[参数设置]
postgresql.conf配置参数:
wal_level = replica
archive_mode = on
archive_command = ‘test ! -f /pgdata/archive/%f && cp %p /pgdata/archive/%f’ #Linux
archive_command = ‘copy “%p” “D:\archive\%f”’ #Windows
fsync = on
min_wal_size = 4G
max_wal_size = 64G
- minimal => 不能通过基础备份和wal日志恢复数据库;
- replica => 该级别支持wal归档和复制;
- logical => 在replica级别的基础上添加了支持逻辑解码所需的信息;
②fsync 可以通过打开强制同步来实现数据安全保证
[触发切换]
- 手动切换 WAL 日志。执行 pg_switch_xlog() 后,WAL 会切换到新的日志,这时会将老的 WAL日志归档;
- WAL 日志写满后触发归档;
- 设置了archive_timeout。假如设置 archive_timeout=60 ,那么每 60 s ,会触发一次 WAL 日志切换,同时触发日志归档;
[优化手段]
PostgreSQL在写入频繁的场景中,会产生大量的WAL日志,而且WAL日志量会远远超过实际更新的数据量。 我们可以把这种现象起个名字,叫做“WAL写放大”,造成WAL写放大的主要原因有2点。
- 在checkpoint之后第一次修改页面,需要在WAL中输出整个page,即全页写(full page writes)。全页写的目的是防止在意外宕机时出现的数据块部分写导致数据库无法恢复。
- 更新记录时如果新记录位置(ctid)发生变更,索引记录也要相应变更,这个变更也要记入WAL。更严重的是索引记录的变更又有可能导致索引页的全页写,进一步加剧了WAL写放大。
在应用的写负载不变的情况下,减少WAL生成量主要有下面几种办法:
- 延长checkpoint时间间隔
FPI(Full Page Image)产生于checkpoint之后第一次变脏的page,在下次checkpoint到了之前,已经输出过PFI的page是不需要再次输出FPI。因此checkpoint时间间隔越长,FPI产生的频度会越低。增大checkpoint_timeout和max_wal_size可以延长checkpoint时间间隔。

※ 如果是checkpoint之后第一次修改页面,则输出整个page的内容(即full page image,简称FPI)。但是page中没有数据的hole部分会被排除,如果设置了wal_compression = on还会对这page上的数据进行压缩。
副作用: 延长checkpoint间隔时间会导致crash恢复时间变长。crash恢复时需要回放的WAL日志量一般小于max_wal_size的一半,WAL回放速度(wal_compression=on时)一般是50MB/s~150MB/s之间。可以根据可容忍的最大crash恢复时间,估算出允许的max_wal_size的最大值。 - 增加HOT_UPDATE比例
普通的UPDATE经常需要更新2个数据块,并且可能还要更新索引page,这些又都有可能产生FPI。而HOT_UPDATE只修改1个数据块,需要写的WAL量也大大减少。
HOT_UPDATE比例过低的一个很常见的原因是更新频繁的表的fillfactor设置不恰当。fillfactor的默认值为100%,可以先将其调整为90%。通过如下方法可以查询表的fillfactor参考值:
select 1 - relpages/reltuples max_fillfactor from pg_class where relname='
';
更新频繁表的fillfactor值修改:
alter table
set (fillfactor=90);
vacuum full
;
副作用:设置过小的fillfactor值将会浪费更多存储空间。不过频繁更新的表设置为fillfactor值为100%也会有dead tuple存在,它并不会比设置合适的fillfactor值节省空间。 - Compression压缩
PostgreSQL9.5新增加了一个wal_compression参数,设为on可以对FPI进行压缩,削减WAL的大小。另外还可以在外部通过SSL/SSH的压缩功能减少主备间的通信流量,已经自定义归档脚本对归档的WAL进行压缩。
PostgreSQL参数变更:
alter system set wal_compression = on
副作用:启用Compression压缩会消耗一定的CPU资源,但几乎可以忽略。
[日志结构]
pg_waldump查询
pg_waldump -p
-s [-n
<截取数量>
]
※ Redo Point可以通过pg_controldata获取的。
截取数量>
[清理方法]
- 自动清理
- 启动数据库时、以及实施检查点时;
- 启动startup进程时,自动清理当前时间线以前的XLOG文件;
- 手动清理
- 检查哪些WAL日志已经持久化
pg_controldata
※ Latest checkpoint’s REDO WAL file 就是最后的日志,之前的可以删除。
- pg_archivecleanup 清理掉指定文件之前的log
pg_archivecleanup -d %PGDATA%\pg_wal
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/226407.html原文链接:https://javaforall.net
