Write-Ahead Logging is one of the core reliability features of PostgreSQL. Every change made to data is first recorded in WAL so that the database can recover after crashes and support replication.
When workloads generate many full-page writes, WAL volume can grow quickly. PostgreSQL provides the wal_compression setting to reduce that overhead by compressing full-page images written into WAL.
Modern PostgreSQL builds support multiple compression methods:
This article explains how to benchmark these methods using a real workload and includes measured results from a 500,000-row update test.
What Is wal_compression?
The wal_compression parameter controls whether PostgreSQL compresses full-page images before writing them to WAL.
You can check the active value with:
SHOW wal_compression;
Result :
wal_compression
-----------------
off
(1 row)
You can inspect more metadata of wal_compression like this:
SELECT *
FROM pg_settings
WHERE name = 'wal_compression';
Result :
name | wal_compression
setting | off
unit |
category | Write-Ahead Log / Settings
short_desc | Compresses full-page writes written in WAL file with specified method.
extra_desc |
context | superuser
vartype | enum
source | configuration file
min_val |
max_val |
enumvals | {pglz,lz4,zstd,on,off}
boot_val | off
reset_val | zstd
sourcefile | /etc/postgresql/18/main/postgresql.conf
sourceline | 247
pending_restart | f
Typical supported values:
Why Benchmark It?
Choosing the right WAL compression method can affect:
- Update speed
- WAL bytes generated
- Replication bandwidth
- Archive storage usage
- Overall write performance
Compression adds CPU work, but it can reduce disk I/O significantly. The best choice depends on the balance between CPU and storage performance.
Test Workload Used
The benchmark was executed on a database named walbench using a table with 500,000 rows.
Each test performed a full-table update on a large text column.
Create the Benchmark Table
DROP TABLE IF EXISTS wal_test;
CREATE TABLE wal_test (
id serial PRIMARY KEY,
data text
);
Insert 500,000 Rows
INSERT INTO wal_test(data)
SELECT repeat(md5(random()::text), 50)
FROM generate_series(1, 500000);
This creates a large enough dataset to generate meaningful WAL traffic.
Benchmark Method
For each compression method, the same sequence was used:
- Confirm the compression mode
- Run CHECKPOINT
- Capture WAL statistics
- Capture starting LSN
- Run the update
- Capture ending LSN
- Capture WAL statistics again
- Compare runtime and WAL generated
Queries Used for Every Test
SHOW wal_compression;
CHECKPOINT;
SELECT * FROM pg_stat_wal;
SELECT pg_current_wal_lsn();
UPDATE wal_test
SET data = repeat(md5(random()::text), 50);
SELECT * FROM pg_stat_wal;
SELECT pg_current_wal_lsn();
SELECT pg_wal_lsn_diff('END_LSN', 'START_LSN');
Test Result: wal_compression = off
The database generated the largest amount of WAL and had the slowest runtime.
walbench=# show wal_compression;
wal_compression
-----------------
off
(1 row)
Time: 0.343 ms
walbench=# checkpoint;
CHECKPOINT
Time: 11.539 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
35/71882270
(1 row)
Time: 0.442 ms
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
21744457 | 3204988 | 16282412215 | 1372404 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.489 ms
walbench=# UPDATE wal_test
SET data = repeat(md5(random()::text), 50);
UPDATE 500000
Time: 17125.486 ms (00:17.125)
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
23244564 | 3334064 | 18014676487 | 1551719 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.295 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
36/5E3C700
(1 row)
Time: 0.272 ms
walbench=# SELECT pg_wal_lsn_diff('36/5E3C700', '35/71882270');
pg_wal_lsn_diff
-----------------
2489033872
(1 row)
Measured results:
- Execution time: 17.125 seconds
- LSN WAL generated: 2,489,033,872 bytes
- pg_stat_wal bytes increase: 1,732,264,272 bytes
This mode produced the highest WAL overhead and the slowest update performance.
Test Result: wal_compression = pglz
Now change the value of the wal_compression like this
Check the path of the postgres conf file like this
show config_file ;
Result :
config_file
-----------------------------------------
/etc/postgresql/18/main/postgresql.conf
(1 row)
Edit that file
sudo nano /etc/postgresql/18/main/postgresql.conf
Change value like this
wal_compression = on # enables compression of full-page writes;
This built-in compression method reduced WAL volume and improved execution time.
walbench=# show wal_compression ;
wal_compression
-----------------
pglz
(1 row)
Time: 0.174 ms
walbench=# checkpoint ;
sCHECKPOINT
Time: 393.790 ms
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
27318177 | 4642270 | 21905768889 | 1666304 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.368 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
36/C43BBF38
(1 row)
Time: 0.324 ms
walbench=#
UPDATE wal_test
SET data = repeat(md5(random()::text), 50);
UPDATE 500000
Time: 10869.771 ms (00:10.870)
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
36/FCCC28F0
(1 row)
Time: 0.335 ms
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
28928285 | 4791274 | 22872241039 | 1696488 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.360 ms
walbench=# SELECT pg_wal_lsn_diff('36/FCCC28F0', '36/C43BBF38');
pg_wal_lsn_diff
-----------------
948988344
(1 row)
Time: 0.413 ms
Measured results:
- Execution time: 10.870 seconds
- LSN WAL generated: 948,988,344 bytes
- pg_stat_wal bytes increase: 966,472,150 bytes
This was a major improvement over off.
Test Result: wal_compression = lz4
Now change the value of the wal_compression like this
Check the path of the postgres conf file like this
show config_file ;
Result :
config_file
-----------------------------------------
/etc/postgresql/18/main/postgresql.conf
(1 row)
Edit that file
sudo nano /etc/postgresql/18/main/postgresql.conf
Change value like this
wal_compression = lz4 # enables compression of full-page writes;
This mode delivered the fastest runtime in the benchmark.
walbench=# show wal_compression;
wal_compression
-----------------
lz4
(1 row)
Time: 0.255 ms
walbench=# checkpoint;
CHECKPOINT
Time: 10.248 ms
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
29637839 | 4930637 | 22963065382 | 1696488 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.732 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
37/2638728
(1 row)
Time: 0.283 ms
walbench=# UPDATE wal_test
SET data = repeat(md5(random()::text), 50);
UPDATE 500000
Time: 7530.834 ms (00:07.531)
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
31139239 | 5058406 | 23907451856 | 1804991 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.449 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
37/3B1BC528
(1 row)
Time: 0.271 ms
walbench=# SELECT pg_wal_lsn_diff('37/3B1BC528', '37/2638728');
pg_wal_lsn_diff
-----------------
951598592
(1 row)
Time: 0.438 ms
Measured results:
- Execution time: 7.531 seconds
- LSN WAL generated: 951,598,592 bytes
- pg_stat_wal bytes increase: 944,386,474 bytes
This mode combined excellent speed with low WAL generation.
Test Result: wal_compression = zstd
Now change the value of the wal_compression like this
Check the path of the postgres conf file like this
show config_file ;
Result :
config_file ----------------------------------------- /etc/postgresql/18/main/postgresql.conf(1 row)
Edit that file
sudo nano /etc/postgresql/18/main/postgresql.conf
Change value like this
walbench=# show wal_compression ;
wal_compression
-----------------
zstd
(1 row)
Time: 0.156 ms
walbench=# checkpoint ;
CHECKPOINT
Time: 189.495 ms
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
31473600 | 5435756 | 24016797437 | 1805319 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.411 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
37/42935C68
(1 row)
Time: 0.347 ms
walbench=# UPDATE wal_test
SET data = repeat(md5(random()::text), 50);
UPDATE 500000
Time: 9306.786 ms (00:09.307)
walbench=# select * from pg_stat_wal;
wal_records | wal_fpi | wal_bytes | wal_buffers_full | stats_reset
-------------+---------+-------------+------------------+----------------------------------
32980468 | 5494169 | 24939172650 | 1840121 | 2026-04-22 13:47:27.486327+05:30
(1 row)
Time: 0.368 ms
walbench=# SELECT pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
37/7AFF8000
(1 row)
Time: 0.246 ms
walbench=# SELECT pg_wal_lsn_diff('37/7AFF8000', '37/42935C68');
pg_wal_lsn_diff
-----------------
946611096
(1 row)
Time: 0.426 ms
Measured results:
- Execution time: 9.307 seconds
- LSN WAL generated: 946,611,096 bytes
- pg_stat_wal bytes increase: 922,375,213 bytes
This mode achieved the best compression result in the benchmark.
Using the latest benchmark run:
- off was the slowest and generated the most WAL.
- pglz was much better than off and significantly reduced WAL overhead.
- lz4 was the fastest option overall.
- zstd produced the smallest WAL volume.
Choose lz4 if You Need Maximum Speed
Best for:
- OLTP systems
- Frequent updates
- High transaction workloads
- Low-latency write performance
Based on this benchmark, lz4 was the best overall performance option.
Choose zstd if You Need Best Compression
Best for:
- WAL archiving
- Replication over limited bandwidth
- Storage-sensitive systems
It slightly trailed lz4 in speed but generated the least WAL.
Choose pglz for Compatibility
Best for:
- Older environments
- Systems without external compression libraries
It still performed well and clearly beat off.
Avoid off for Heavy Update Workloads
Without compression, PostgreSQL wrote much more WAL data and needed more time to finish the same update.
Why Compression Improved Speed
Many users expect compression to slow down writes because it uses CPU cycles. In practice, storage I/O is often the bigger bottleneck.
By reducing WAL volume, PostgreSQL writes fewer bytes to disk. That lower I/O cost can outweigh compression overhead, leading to faster total execution time.
That is exactly what happened in this benchmark.
Recommended Configuration
For speed-focused systems:
ALTER SYSTEM SET wal_compression = 'lz4';
SELECT pg_reload_conf();
For storage-focused systems:
ALTER SYSTEM SET wal_compression = 'zstd';
SELECT pg_reload_conf();
For built-in compatibility:
ALTER SYSTEM SET wal_compression = 'pglz';
SELECT pg_reload_conf();
How to Reproduce This Benchmark Yourself
- Create a large test table
- Insert enough rows
- Change wal_compression
- Run CHECKPOINT
- Execute identical updates
- Measure runtime
- Compare pg_stat_wal
- Compare pg_wal_lsn_diff()
Always test on your own workload before changing production settings.
This benchmark shows that WAL compression is not only about saving storage. It can also improve performance.
From the measured results:
- lz4 was the fastest
- zstd generated the least WAL
- pglz was a strong middle option
- off performed worst under the same workload
For modern PostgreSQL systems, enabling wal_compression with lz4 or zstd is often a better choice than leaving it disabled.