Introduction
This post shows a way to compare the content of a PostgreSQL block from memory and from its related file. To achieve this goal we will use the pageinspect extension and pg_filedump.
Let’s compare
Find a dirty block
First of all, for this example, I want to be sure that I will be comparing a dirty block (means its content between the shared buffer and the file it belongs to differs).
A dirty block is a block that has been modified since the last checkpoint.
Let’s use the pg_buffercache extension to find a dirty block that belongs to the relation bdttab:
postgres=# select c.relname, b.relblocknumber, b.isdirty from pg_buffercache AS b, pg_class AS c where c.relfilenode = b.relfilenode and c.relname = 'bdttab' and isdirty is true;
relname | relblocknumber | isdirty
---------+----------------+---------
bdttab | 1 | t
(1 row)
So the block number 1 is a dirty block, let’s compare its content from its file and memory.
Get the content from the relation file
To do so, let’s get its associated filepath that way:
postgres=# SELECT pg_relation_filepath('bdttab');
pg_relation_filepath
----------------------
base/13580/32771
and then use pg_filedump that way:
$ ./pg_filedump -R 1 /home/postgres/pg/pg_installed/data/base/13580/32771
*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility
*
* File: /home/postgres/pg/pg_installed/data/base/13580/32771
* Options used: -R 1
*******************************************************************
Block 1 ********************************************************
<Header> -----
Block Offset: 0x00002000 Offsets: Lower 84 (0x0054)
Block: Size 8192 Version 4 Upper 7592 (0x1da8)
LSN: logid 0 recoff 0x01951ae0 Special 8192 (0x2000)
Items: 15 Free Space: 7508
Checksum: 0xc042 Prune XID: 0x00000000 Flags: 0x0000 ()
Length (including item array): 84
<Data> -----
Item 1 -- Length: 40 Offset: 8152 (0x1fd8) Flags: NORMAL
Item 2 -- Length: 40 Offset: 8112 (0x1fb0) Flags: NORMAL
Item 3 -- Length: 40 Offset: 8072 (0x1f88) Flags: NORMAL
Item 4 -- Length: 40 Offset: 8032 (0x1f60) Flags: NORMAL
Item 5 -- Length: 40 Offset: 7992 (0x1f38) Flags: NORMAL
Item 6 -- Length: 40 Offset: 7952 (0x1f10) Flags: NORMAL
Item 7 -- Length: 40 Offset: 7912 (0x1ee8) Flags: NORMAL
Item 8 -- Length: 40 Offset: 7872 (0x1ec0) Flags: NORMAL
Item 9 -- Length: 40 Offset: 7832 (0x1e98) Flags: NORMAL
Item 10 -- Length: 40 Offset: 7792 (0x1e70) Flags: NORMAL
Item 11 -- Length: 40 Offset: 7752 (0x1e48) Flags: NORMAL
Item 12 -- Length: 40 Offset: 7712 (0x1e20) Flags: NORMAL
Item 13 -- Length: 40 Offset: 7672 (0x1df8) Flags: NORMAL
Item 14 -- Length: 40 Offset: 7632 (0x1dd0) Flags: NORMAL
Item 15 -- Length: 40 Offset: 7592 (0x1da8) Flags: NORMAL
*** End of Requested Range Encountered. Last Block Read: 1 ***
Now let’s get the information from memory (means from the shared buffer).
Get the content from memory
First, let’s dump the block from memory to a file thanks to the get_raw_page() pageinspect’s function that way:
$ psql postgres -tA -c "select encode(get_raw_page::bytea, 'hex') from get_raw_page('bdttab',1)" | xxd -r -p > bdttab_block_1.out
$ du -sh bdttab_block_1.out
8.0K bdttab_block_1.out
As you can see the generated file size is 8K, which is the default PostgreSQL block size (that i did not change).
Now we can use the dumped block as the pg_filedump input that way:
$ ./pg_filedump ./bdttab_block_1.out
*******************************************************************
* PostgreSQL File/Block Formatted Dump Utility
*
* File: ./bdttab_block_1.out
* Options used: None
*******************************************************************
Block 0 ********************************************************
<Header> -----
Block Offset: 0x00000000 Offsets: Lower 88 (0x0058)
Block: Size 8192 Version 4 Upper 7552 (0x1d80)
LSN: logid 0 recoff 0x0197c9b0 Special 8192 (0x2000)
Items: 16 Free Space: 7464
Checksum: 0xc042 Prune XID: 0x00000000 Flags: 0x0000 ()
Length (including item array): 88
<Data> -----
Item 1 -- Length: 40 Offset: 8152 (0x1fd8) Flags: NORMAL
Item 2 -- Length: 40 Offset: 8112 (0x1fb0) Flags: NORMAL
Item 3 -- Length: 40 Offset: 8072 (0x1f88) Flags: NORMAL
Item 4 -- Length: 40 Offset: 8032 (0x1f60) Flags: NORMAL
Item 5 -- Length: 40 Offset: 7992 (0x1f38) Flags: NORMAL
Item 6 -- Length: 40 Offset: 7952 (0x1f10) Flags: NORMAL
Item 7 -- Length: 40 Offset: 7912 (0x1ee8) Flags: NORMAL
Item 8 -- Length: 40 Offset: 7872 (0x1ec0) Flags: NORMAL
Item 9 -- Length: 40 Offset: 7832 (0x1e98) Flags: NORMAL
Item 10 -- Length: 40 Offset: 7792 (0x1e70) Flags: NORMAL
Item 11 -- Length: 40 Offset: 7752 (0x1e48) Flags: NORMAL
Item 12 -- Length: 40 Offset: 7712 (0x1e20) Flags: NORMAL
Item 13 -- Length: 40 Offset: 7672 (0x1df8) Flags: NORMAL
Item 14 -- Length: 40 Offset: 7632 (0x1dd0) Flags: NORMAL
Item 15 -- Length: 40 Offset: 7592 (0x1da8) Flags: NORMAL
Item 16 -- Length: 40 Offset: 7552 (0x1d80) Flags: NORMAL
*** End of File Encountered. Last Block Read: 0 ***
As you can see, and as expected in this example as we choose a dirty block, the blocks coming from the shared buffer and from the file are different (among other things, there is one more item in memory).
Conclusion
Thanks to pageinspect and pg_filedump we have been able to compare a block content from memory and from the file it belongs to.