前言
在16MB段大小的前提下,WAL文件的排列大概是这个样子的:
WAL文件的命名规则是什么样的?WAL中的日志序列号的编号规则又是什么样的?我们可以按照wal段大小以及PG中文件的页(块)大小分类,进行综合分析,然后得出结论。
验证与分析
我们要从wal-segsize段大小这个值开始引入这个话题,initdb下有一个蛮重要的参数:
--wal-segsize=
Set the WAL segment size, in megabytes. This is the size of each individual file in the WAL log. The default size is 16 megabytes. The value must be a power of 2 between 1 and 1024 (megabytes). This option can only be set during initialization, and cannot be changed later.
这个参数值的大小,可以是从2的0次方一直到2的10次方,即从1MB到1024MB。一旦初始化完成之后,就不能再更改了。
而默认值是16MB,即2的4次方。
结合configure 编译时配置的:
--with-blocksize=BLOCKSIZE
set table block size in kB [8]默认是8[kB],有多种组合,它意味着是2^^13页内地址。
默认page size为8kB下的分析
16MB wal seg下的WAL段文件命名及LSN编码规则
WAL段文件名是由24位16进制数字组成(3组*8位HEX),以使用默认的段(wal seg)大小16MB为例 ,
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FA
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FB
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FC
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FD
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FE
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 0000000100000002000000FF
-rw------- 1 postgres postgres 16777216 Apr 16 04:21 000000010000000300000000
00000001 00000002 000000FE
分成三段,分别是时间线:'00000001'; 32bit的逻辑日志号:'00000002'; 32bit的日志段号:'000000FE'
我们也可以看到,上边的日志段号FE->FF然后又回到了00,但是这个时候日志号会从00000002递增到了00000003。
再看看当前的LSN号与WAL文件的对应关系:
mydb=# select pg_current_wal_lsn() as lsn, pg_walfile_name(pg_current_wal_lsn()) as filename, pg_walfile_name_offset(pg_current_wal_lsn()) as lsn_offset;
lsn | filename | lsn_offset
------------+--------------------------+-------------------------------------
3/3A993360 | 00000001000000030000003A | (00000001000000030000003A,10040160)
(1 row)
mydb=# insert into t select n, 'test' || n from generate_series(1, 550000) as n; INSERT 0 550000
mydb=# select pg_current_wal_lsn() as lsn, pg_walfile_name(pg_current_wal_lsn()) as filename, pg_walfile_name_offset(pg_current_wal_lsn()) as lsn_offset;
lsn | filename | lsn_offset
------------+--------------------------+-----------------------------------
3/3D023F10 | 00000001000000030000003D | (00000001000000030000003D,147216)
(1 row)
看看上边的LSN: 3/3D023F10 , 它是一个64位的BIGINT,划分为4部分:
32位 | 8位 | 11位 | 13位 |
---|---|---|---|
逻辑日志文件号: 00000003 | wag seg号:0000003D | 块号 | 偏移量 |
计算下3/3D023F10的偏移量:
mydb=# select x'023F10'::int;
int4
--------
147216
(1 row)
注意,这是因为这里采用的是8K的块, 所以用13位来表示块号后的偏移量。
但是这个023F10也只是个总体的偏移量,要想得到3D这个段文件里头具体哪一块,块内偏移,将这个二进制切成(11bit, 13bit)两部分,分别求值即可。
mydb=# select x'023F10';
?column?
--------------------------
000000100011111100010000
(1 row)
mydb=# select B'00000010001'::int as blkno, B'1111100010000'::int as address;
blkno | address
-------+---------
17 | 7952
(1 row)
为什么上边段号最大是000000FF,然后重新回到00000000,这是因为一个段是16MB, 2^^24,最多总共提供2^^32的空间,只能分成2^^8 = 256个段。
64MB wal seg下的WAL段文件命名及LSN编码规则
如果采用的是 --wal-segsize=64初始化,你得到的WAL文件应该是下边这个样子的:
initdb --wal-segsize=64
并没有等到段号积到000000FF,再回到00,而是到了3F,就直接回到00,并且日志编号加1。这是因为2^^32(寻址)/2^^26 (段大小) = 2^6 = 64 = 0X3F + 1.
再来看此规则下的LSN求算:
mydb=# select pg_current_wal_lsn() as lsn, pg_walfile_name(pg_current_wal_lsn()) as filename, pg_walfile_name_offset(pg_current_wal_lsn()) as lsn_offset;
lsn | filename | lsn_offset
------------+--------------------------+------------------------------------
2/144F4488 | 000000010000000200000005 | (000000010000000200000005,5194888)
(1 row)
我们看看LSN值:2/144F4488,也把它划分为4部分:
32位 | 6位 | 13位 | 13位 |
---|---|---|---|
逻辑日志文件号: 00000002 | 000101: 即为5 | B'0001001111010' | B'0010010001000' |
原来是8位值来表示段号的,那是16MB为一个段,现在是64MB为一个段,我们把144F4488展开成32bit:
0001 01, 00 0100 1111 0100 0100 0111 0111
后边的13位块号,以及块内偏移也是13位。
再看具体偏移:
mydb=# select (x'144F4488');
?column?
----------------------------------
00010100010011110100010010001000
(1 row)
mydb=# select B'00010011110100010010001000'::int;
int4
---------
5194888
(1 row)
去掉最左边高位的6个bit,剩余部分,刚好就是offset: 5194888。这只是一个笼统的逻辑偏移,如果再细究,我们可以再算一下:
mydb=# select B'0001001111010'::int as blkno, B'0010010001000'::int as address;
blkno | address
-------+---------
634 | 1160
(1 row)
意思是说它现在位于时间线1下的日志逻辑号为2下的,第5段的blkno为634,偏移量为1160。
对此,嗯,需不需要用pg_waldump来验证一下?对目标段文件:000000010000000200000005?
[06:32:22-postgres@sean-rh3:/opt/pg/14/d1/pg_wal]$ pg_waldump 000000010000000200000005 | grep 144F4488
rmgr: Heap len (rec/tot): 65/ 65, tx: 753, lsn: 2/144F4488, prev 2/144F4450, desc: INSERT off 119 flags 0x01, blkref #0: rel 1663/16384/16386 blk 717300
rmgr: Heap len (rec/tot): 65/ 65, tx: 753, lsn: 2/144F44D0, prev 2/144F4488, desc: INSERT off 120 flags 0x00, blkref #0: rel 1663/16384/16386 blk 717300
我们也确实看不到blkno的信息,但是相当于是在64MB的空间里头,进行切分,第634页里头的一个偏移,这么理解就相对合理一些。
我们再看下段文件更小一些的情况。
4MB wal seg大小下的WAL段文件命名及LSN编码规则
initdb --wal-segsize=4
为了看到结果,我把wal log参数设为:
wal_keep_size = 5120
## 让它大于4G
mydb=# insert into t select n, 'test' || n from generate_series(1, 30000000) as n;
INSERT 0 30000000
mydb=# insert into t select n, 'test' || n from generate_series(1, 30000000) as n;
INSERT 0 30000000
看看这里头的段号:是从3FF回到0的。按照寻址空间的原理:2^^32 / 2^^22 = 2^^ 10 = X'03FF' + 1
这样一下子就清楚了。
mydb=# \timing on
Timing is on.
mydb=# insert into t select n, 'test' || n from generate_series(1, 30000000) as n;
INSERT 0 30000000
Time: 36951.270 ms (00:36.951)
我们再看下LSN的计算,看看这个LSN:2/893B3ED8
mydb=# select pg_current_wal_lsn() as lsn, pg_walfile_name(pg_current_wal_lsn()) as filename, pg_walfile_name_offset(pg_current_wal_lsn()) as lsn_offset;
lsn | filename | lsn_offset
------------+--------------------------+------------------------------------
2/893B3ED8 | 000000010000000200000224 | (000000010000000200000224,3882712)
(1 row)
Time: 0.359 ms
2/893B3ED8, 表示段号的应该是10位,而不是默认的8位。
mydb=# select X'893B3ED8';
?column?
----------------------------------
1000100100 111011001 1111011011000
(1 row)
mydb=# select B'1000100100'::int;
int4
------
548
(1 row)
Time: 0.226 ms
mydb=# select to_hex(B'1000100100'::int);
to_hex
--------
224
(1 row)
mydb=# select B'111011001'::int as blkno, B'1111011011000'::int as address;
blkno | address
-------+---------
473 | 7896
(1 row)
得到下给的值。
2位 | 10位 | 9位 | 13位 |
---|---|---|---|
逻辑日志文件号: 00000002 | B'1000100100': 即为:X('224') | B'111011001' 即blk: 473 | B'1111011011000'即地址:7896 |
回过头来看看,上边的结果都是在block size (page size) = 8K下得到的结果。
如果page size = 16K,那么上边的计算方法,有一个地方是需要调整的,就是末尾的那个13位的页内偏移要改为14位(2^^14 = 16K)。其它的规则基本上是不变的。
默认page size为16kB下wal段为8MB的分析
我们先编译出一份页大小为16kB的PG,仅用于分析。
wget https://ftp.postgresql.org/pub/source/v14.7/postgresql-14.7.tar.gz
tar zxf postgresql-14.7.tar.gz
cd postgresql-14.7
su
yum install -y readline readline-devel flex bison openssl openssl-devel
mkdir build
cd build
../configure -with-extra-version=" [Sean]" --prefix=/usr/pgsql-14.7build --with-blocksize=16
make -j 4 world-bin
su -c "make install-world-bin"
设置好环境变量:
[07:17:13-postgres@sean-rh3:/opt/pg]$ cat env14build.sh
export PGROOT=/usr/pgsql-14.7build
export PGHOME=/var/lib/pgsql/14
export PGPORT=5555
export PGDATA=$PGHOME/data
export PATH=$PGROOT/bin:$PATH
export LD_LIBRARY_PATH=$PGROOT/lib:$LD_LIBRARY_PATH
我们就试一下wal段大小为8MB的结果
initdb --wal-segsize=8
为了确保wal文件被覆盖,略改下配置文件:
vi postgresql.conf
archive_mode = off
wal_keep_size = 5120
生成数据:
mydb=# \timing on
Timing is on.
mydb=# create table t(id int, col2 text);
CREATE TABLE
Time: 5.584 ms
mydb=# insert into t select n, 'test'||n from generate_series(1, 30000000) as n;
INSERT 0 30000000
Time: 30704.232 ms (00:30.704)
mydb=# insert into t select n, 'test'||n from generate_series(1, 30000000) as n;
INSERT 0 30000000
Time: 37620.341 ms (00:37.620)
2^^32 / 2^^23 = 2^^9 = X'1FF' + 1,应该是到了1FF (9bit),就会回到000的段号,同时日志号加1。
如下图:
顺道看一下data file:
mydb=# select setting || '/' || pg_relation_filepath('t') from pg_settings where name='data_directory';
?column?
-----------------------------------------
/var/lib/pgsql/14/data/base/16384/16386
mydb=# \! ls -la /var/lib/pgsql/14/data/base/16384/16386*
-rw------- 1 postgres postgres 1073741824 Apr 16 08:37 /var/lib/pgsql/14/data/base/16384/16386
-rw------- 1 postgres postgres 1073741824 Apr 16 08:37 /var/lib/pgsql/14/data/base/16384/16386.1
-rw------- 1 postgres postgres 822886400 Apr 16 08:39 /var/lib/pgsql/14/data/base/16384/16386.2
-rw------- 1 postgres postgres 409600 Apr 16 08:37 /var/lib/pgsql/14/data/base/16384/16386_fsm
-rw------- 1 postgres postgres 49152 Apr 16 08:37 /var/lib/pgsql/14/data/base/16384/16386_vm
差不多1000万条记录有450M~500M的样子。
我们来分析一下LSN号:(1/3F9E448)
mydb=# select pg_current_wal_lsn() as lsn, pg_walfile_name(pg_current_wal_lsn()) as filename, pg_walfile_name_offset(pg_current_wal_lsn()) as lsn_offset;
lsn | filename | lsn_offset
-----------+--------------------------+------------------------------------
1/3F9E448 | 000000010000000100000007 | (000000010000000100000007,7988296)
(1 row)
mydb=# select X'3F9E448' as offset;
offset
------------------------------
00111 1111001111 0010001001000
(1 row)
看一下下表:
32位 | 9位(段号) | 9位 (块号) | 14位(块内偏移) |
---|---|---|---|
逻辑日志文件号: 00000001 | 00111: 即为X'7' | B'1111001111' | B'0010001001000' |
因为是8KB的页大小,所以页内地址应该是13位。段编号去了9位。页号应该是32 - 13 - 9 = 10位。
用PG算一算结果:
-- 7988296这个综合offset值的得来
select B'11110011110010001001000'::int as offset;
mydb=# select B'11110011110010001001000'::int as offset;
offset
---------
7988296
(1 row)
select B'00111'::int as segno, B'1111001111'::int as blk_no, B'0010001001000'::int as blk_address;
mydb=# select B'00111'::int as segno, B'1111001111'::int as blk_no, B'0010001001000'::int as blk_address;
segno | blk_no | blk_address
-------+--------+-------------
7 | 975 | 1096
(1 row)
至此,我们完整的验证完毕。
参考
https://www.postgresql.org/docs/15/app-initdb.html