pgsql的wal log

参考:

http://mysql.taobao.org/monthly/2017/03/02/

http://www.interdb.jp/pg/pgsql09.html

事务日志是数据库的重要组成部分,因为即使在发生系统故障时,我们也要求所有数据库管理系统不丢失任何数据。它是一个记录数据库系统中所有更改和操作的历史日志,以确保没有数据由于故障(例如电源故障或其他导致服务器崩溃的服务器故障)而丢失。由于日志包含关于已经执行的每个事务的足够信息,因此在服务器崩溃时,数据库服务器应该能够通过重新应用事务日志中的更改和操作来恢复数据库集群。在计算机科学领域,WAL是 预写式日志日志(Write Ahead Logging)的首字母缩写, 预写式日志(Write Ahead log)是将更改和操作写入事务日志的协议或规则。在这里,这个术语被用作事务日志的同义词,也用来指与将操作写入事务日志(WAL)相关的实现机制。

常见的事务日志有redo log和undo log。 postgresql不支持undo log。

 

WAL

write ahead log: 预写式日志,也被称为xlog。和oracle的redo日志类似,存放在$PGDATA/pg_xlog中,10版本以后在$PGDATA/pg_wal目录。如果开启了归档,在目录archive_status下会有一些文件,以ready结尾的,表示可以归档但还没有归档,done结尾的表示已经归档。

postgresql在7.1版本中首次实现了WAL机制,以减轻服务器崩溃的影响。它还使时间点恢复(PITR)和流复制(SR)的实现成为可能。


postgresql中的WAL机制的核心思想就是:先日志落盘,后数据落盘。就是在写数据到磁盘里成为固定数据之前,先写入到日志里,然后一定条件下触发调用fsync()将此数据刷到磁盘。

 

机制:

wal这种机制不需要在每次事务提交的时候都把数据页刷到磁盘,如果出现数据库崩溃, 我们可以用日志来恢复数据库,任何尚未附加到数据页的记录都将先从日志记录中重做(这叫向前滚动恢复,也叫做 REDO)。对于PostgreSQL来说,未采用WAL机制之前,如果数据库崩溃,可能存在数据页不完整的风险,而WAL 在日志里保存整个数据页的内容,就能解决这个问题。

性能问题:

WAL机制可以从两个方面来提高性能:

  • 多个client写日志文件可以通过一次 fsync()来完成
  • 日志文件是顺序写的,同步日志的开销要远比同步数据页的开销要小

注:①WAL 日志文件默认为 16MB

WAL机制的实现

实现WAL机制,需要保证脏页在刷新到磁盘前,该数据页相对应的日志记录已经刷新到磁盘中。为了实现WAL机制,当PostgreSQL进行事务提交(脏数据页需要刷新到磁盘)时,需要进行如下操作:

  • 生成该事务提交的日志记录(唯一标示为LSN–Log sequence number)
  • 将该LSN之前的xlog日志刷入到磁盘中

在每个数据页的PageHeaderData结构中有一个pd_lsn, 用于标识对该页的最后一次更改。

PageHeaderData结构:

pgsql的wal log_第1张图片

PageXLogRecPtr结构和每个日志记录一一对应,同时LSN是全局统一管理,顺序增加的。当缓冲区管理器(Bufmgr)写出脏数据页时,必须确保小于页面PageHeaderData中pd_lsn指向的Xlog日志已经刷写到磁盘上了。

WAL共享缓存区

为了进一步的减少xlog日志文件的I/O操作,PostgreSQL中引入了WAL共享缓存区,对产生的xlog日志进行缓存,合并I/O操作。

因为pg是工作在用户空间,wal log buffer处于用户空间的内存中,要写入到磁盘的log file里,中间要经过操作系统内核空间的os buffer,所以还要经过一次fsync系统调用,将os buffer中的日志刷到磁盘上的log file里。

也即将写wal日志到磁盘过程如下:

wal log Buffer(user space)--->OS Buffer(kernel Space)--->log file on disk

WAL共享缓存区的大小可以通过设置postgresql.conf文件参数wal_buffers来设置。

#cat postgresql.conf |grep wal_buffers
wal_buffers=16MB

可以看出,当多个client同时进行事务提交时,如果这个缓存区比较大,相应地会更大程度地合并I/O,提高性能,但是如果过大,同时也存在断电后,这部分数据丢失的风险。

WAL缓存区可以理解为一个环形的共享缓存,每次缓存空间满后,会将头部的页面刷新到磁盘,同时写入新的页面,并将头部往后移动一位。具体来说,需要写入新的日志记录时:

  • 当WAL缓存区中有足够的空间,顺序写入到缓存区中。
  • 当WAL缓存区写到尾部且空间不足时,从头部刷出信息后重复利用。

因为WAL缓存区是顺序刷出的,这样日志文件中的信息必然是连续的。

为了更好的维护和管理WAL的刷盘,PostgreSQL提供辅助进程Walwriter 预发式日志写进程,Walwriter会周期性地将xlog日志块写入到磁盘。

wal日志刷盘的时间

log buffer中未刷到此判断额日志称为脏日志(dirty log),我们知道pg中事务每次提交的时候都会先刷事务日志到磁盘中,除了在有commit动作后会刷日志到磁盘,还有其他刷日志的规则吗?

  • 事务提交
  • Walwriter进程到达间歇时间
  • 创建checkpoint
  • WAL缓存区满

其中checkpoint会强制把所有数据缓存中的脏页刷新到磁盘,所以对应的xlog日志页必须在这之前刷新到磁盘。

PostgreSQL WAL日志名解析

参考

日志名组成

在PG中日志名是由16进制命名总共24个字符由三部分组成: 时间线ID + LogId+ logSeg

0000000100000001000000C4
00000001 //时间线ID
00000001 //LogId
000000C4 //logSeg

两个函数

pg_current_wal_lsn():获得当前预写式日志写入位置
pg_walfile_name(lsn pg_lsn):转换预写式日志位置为文件名

计算日志名

pg_walfile_name可以根据lsn(pg_current_wal_lsn())来获取文件名。

那么它们之间的转换关系是怎么样的?

转换关系:

WAL segment file name = timelineId +(uint32)LSN−1 / (16M ∗ 256) + (uint32)(LSN − 1 / 16M) % 256

其中:

  • timelineId为时间线
  • (uint32)LSN−1 / (16M ∗ 256)为LogId
  • (uint32)(LSN − 1 / 16M) % 256为LogSeg

timeline获取

#pg_controldata|grep TimeLine
Latest checkpoint's TimeLineID:       2
Latest checkpoint's PrevTimeLineID:   2

lsn获取

注意转换公式中LSN为十进制,而pg_current_wal_lsn()得到的值是十六进制,需要进行转换。


postgres=# select pg_current_wal_lsn();
 pg_current_wal_lsn 
--------------------
 0/22000878
此处22000878为lsn,需要转换成十进制。
postgres=# select x'22000878'::bigint; 
   int8    
-----------
 570427512
(1 row)

LogId获取​

套用公式:(uint32)LSN−1 / (16M ∗ 256)

postgres=# select ((570427512 - 1) / (16 * 1024 * 1024 )) % 256 ;          
 ?column? 
----------
       34
(1 row)
##此时34为10进制,wal文件名组成为16进制,需要再进行转换
postgres=# select to_hex(34); 
 to_hex 
--------
 22

将timeline、LogId、logSet拼接起来,每个占8为,高位补齐。最后结果为:000000020000000000000022。

最后通过pg_walfile_name函数验证结果,符合预期。

postgres=# select pg_walfile_name('0/22000878');
     pg_walfile_name      
--------------------------
 000000020000000000000022
(1 row)

 

日志归档:

参考

两种情况:不开启归档和开启归档。

不开启归档情况下,pg_xlog(或pg_wal目录,10.0以后pg_xlog更名为pg_wal)目录默认文件数和默认大小与某些参数有关。具体什么关系,需要再看下。

而开启了归档后,只有归档成功的pg_xlog(wal)文件才会被清除。

所谓wal日志归档,就是把在线的wal日志备份出来。说起来简单,但是在正式的系统上需要一个完备的归档策略,以备系统所需稳定。

参数设置

  • wal_level = replica
  • archive_mode = on(归档日志开关)
  • archive_command = ‘cp %p /pgdata/10/archive_wals%f’

注:

①wal_level 参数的可选的值有minimal、replica和logical,wal的级别依次增高,在wal的信息也越多。由于minimal这一级别的wal不包含从基础的备份和wal日志重建数据的足够信息,在该模式下,无法开启wal日志归档。

②archive_command 该参数的默认值是一个空字符串,他的值可以是一条shell命令或者一个复杂的shell脚本。在shell脚本或命令中可以用 “%p” 表示将要归档的wal文件包含完整路径的信息的文件名,用“%f” 代表不包含路径信息的wal文件的文件名

③配置了archive_mode=on,但是没有配置archive_command,会造成xlog文件会一直堆积。(xlog写完后,会写.ready,但是由于没有配置archive_command,也就是说不会触发归档命令,所以一直都不会写.done) 

什么时候会自动清理xlog?

启动数据库时、以及实施检查点时。

启动startup进程时,自动清理当前时间线以前的XLOG文件

日志手动清理

1、通过:pg_controldata -D $PGDATA|grep  Latest |grep REDO|grep WAL

命令获取哪个之前的xlog文件可以删除。

如图:

2、使用pg_archivecleanup 清理掉指定文件之前的log

命令: pg_archivecleanup  -d $PGDATA/pg_xlog 0000000200000005000000A5

什么情况下会触发归档?

1、手动切换 WAL 日志。执行 pg_switch_xlog() 后,WAL 会切换到新的日志,这时会将老的 WAL日志归档。

2、WAL 日志写满后触发归档

3、设置了archive_timeout。假如设置 archive_timeout=60 ,那么每 60 s ,会触发一次 WAL 日志切换,同时触发日志归档。

 

 

你可能感兴趣的:(postgresql,wal_log,postgresql,事务日志)