WAL Levels in Postgres and Effective WAL Level in PG19

buraksen1 pts0 comments

WAL Levels in Postgres and Effective WAL Level in PG19 · Burak Sen

LinkedIn

WAL Levels in Postgres and Effective WAL Level in PG19<br>August 11, 2026·6 min readpostgresqldatabases

On this page▾Postgres 19 is to be released in a few months. In June first beta version pg19beta1 and in July second pg19beta2 were released. Postgres 19 includes security, performance, and observability improvements and features that can be seen in the release notes. I want to focus on the new effective_wal_level server variable. However, before doing that let's explore which WAL levels are available in Postgres and what do they mean? Note that, this will be a high-level overview of WAL levels. I want to delve into WAL in future and link it in this blog post. If you know WAL levels, feel free to skip to the Effective WAL Level section or skip the post completely to not lose time :).

WAL (Write-Ahead Log) Levels#

Postgres has wal_level is an enum that can have three values, minimal, replica and logical. Default WAL level is replica. WAL levels are defined in the source code as:

c// src/include/access/xlog.h#L74:74<br>typedef enum WalLevel<br>WAL_LEVEL_MINIMAL = 0,<br>WAL_LEVEL_REPLICA,<br>WAL_LEVEL_LOGICAL,<br>} WalLevel;

There is a mapping:

c// src/backend/access/rmgrdesc/xlogdesc.c<br>const struct config_enum_entry wal_level_options[] = {<br>{"minimal", WAL_LEVEL_MINIMAL, false},<br>{"replica", WAL_LEVEL_REPLICA, false},<br>{"archive", WAL_LEVEL_REPLICA, true}, /* deprecated */<br>{"hot_standby", WAL_LEVEL_REPLICA, true}, /* deprecated */<br>{"logical", WAL_LEVEL_LOGICAL, false},<br>{NULL, 0, false}<br>};

Wait, what is archive and hot_standby and why do they map to replica? First of all, last parameter is hidden field and its only set to true for these two. Postgres 9.6 release notes describes that these two WAL levels are merged into replica and therefore they are deprecated.

Enlarge<br>WAL level stack<br>WAL levels — each is a superset of the one belowlogical+ logical-decoding data (new tuple, replica identity as needed) → logical decoding / CDCreplica(default)+ WAL for new/rewritten relations · standby info → archiving · PITR · physical replicationminimalcrash / immediate-shutdown recovery onlyskips row WAL for relations created or rewritten in the same txn⤷ fast bulk loads · cannot archive, replicate, or decodeWAL detail grows minimal → replica → logical · set with wal_level (server start only)

Before explaining WAL levels, first lets go over these:

Crash Recovery: In a server crash, Postgres replays WAL to recover

Continuous WAL Archiving: Copy completed WAL segments to a durable storage

Point-in-time Recovery: Restore Postgres to a specific time using WAL archiving

Physical Standby: A standby Postgres server which continuously replays primary server's WAL. Hot standby can accept read-only queries.

Logical Decoding: Convert WAL contents into stream of tuples or SQL for ease of understanding

Physical Replication: Byte-for-Byte copy of source and destination servers

Logical Replication: Replicating data objects and changes on them using logical decoding

There are other functionalities affected by WAL and list above needs further explanations. I'll not include them in the scope of this post.

Minimal Level#

This level contains only enough information for crash recovery. Postgres produces the least WAL volume. However, PITR, physical standbys, physical replication, and logical replication cannot be used.

There are several macros used for deciding what to log:

c /* src/include/access/xlog.h */<br>#define XLogIsNeeded() (wal_level >= WAL_LEVEL_REPLICA)

/* src/include/utils/rel.h */<br>#define RelationNeedsWAL(relation) \<br>(RelationIsPermanent(relation) && (XLogIsNeeded() || \<br>(relation->rd_createSubid == InvalidSubTransactionId && \<br>relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId)))

RelationNeedsWAL requires the relation to be permanent. It then requires either wal_level >= replica or neither the relation nor its current physical storage to have been created or replaced in the current transaction. At minimal, page changes to newly created or rewritten physical storage may be skipped because they have no previous committed state. If the transaction aborts, the new storage is discarded. Before commits, PostgreSQL makes the new storage durable by syncing its files or logging full-page images. Postgres does not need the extra WAL records for every page modification.

Enlarge<br>minimal WAL optimization<br>COPY into a freshly-created table (same transaction)wal_level = replica / logicalnew table filepg_walrow 1row 2row 3row 4MULTI_INSERTrows 1–4COPY batches tuples into multi-insert WAL recordswal_level = minimalnew table filepg_walrow 1row 2row 3row 4no per-row WALbelow threshold → full-page imagesat/above threshold → fsync filesCOMMIT chooses one path · default threshold: 2 MBThe optimization fires only for relations created or rewritten in the same transaction(CREATE TABLE AS, COPY into a new table, CLUSTER, TRUNCATE, REINDEX, REFRESH MATERIALIZED...

postgres levels level replica logical physical

Related Articles