DB Internals AtlasPostgreSQL 18.6 Home About

Background processes

WAL writer

The WAL writer wakes up every wal_writer_delay and writes and flushes WAL that backends have not flushed themselves. It matters most when synchronous_commit is off.

In plain words

Every change is first described in a WAL record in the WAL buffers (shared memory). Before PostgreSQL tells you "COMMIT", it normally makes sure that record is flushed to pg_wal on disk. The backend running your transaction does that flush itself.

The WAL writer is a helper that flushes WAL in the background, every wal_writer_delay (200 ms). With normal commits it has little to do. It becomes important when synchronous_commit = off: then COMMIT returns before the WAL is flushed, and the WAL writer is the one that makes those commits durable shortly afterwards.

That gives much faster commits, at a price: if the server crashes, the last fraction of a second of "committed" transactions can be lost (the database stays consistent; it is as if those transactions never happened). Try the selector below and watch the "not yet durable" counter.

For DBAs and operators

One round of XLogBackgroundFlush()

  1. Target = the end of the last completely filled WAL page. If that is already flushed, target = the latest asynchronous commit (so a partially filled page is written for it).
  2. If wal_writer_flush_after = 0, or wal_writer_delay has passed since its last flush, or at least wal_writer_flush_after (1MB) of WAL is unflushed: write and fsync up to the target.
  3. Otherwise only write it to the kernel (cheap) and fsync later.

When an asynchronous commit happens, the backend wakes the WAL writer immediately if it was hibernating or if unflushed WAL has already reached wal_writer_flush_after (or that setting is 0). The documented worst case for an async commit to become durable is three times wal_writer_delay. After 50 idle rounds it hibernates (sleep × 25, wait_event = WalWriterMain either way).

Why it helps with synchronous commit too

Large transactions (bulk loads, CREATE INDEX) fill WAL buffers quickly. If the buffers wrap around before anyone wrote them out, the inserting backend must write WAL itself, counted in pg_stat_wal.wal_buffers_full. The WAL writer reduces that by writing filled pages in the background. A large wal_buffers_full is a sign to raise wal_buffers (default -1 = 1/32 of shared_buffers, max 16MB auto).

Monitoring (PostgreSQL 18)

SELECT wal_records, wal_fpi, wal_bytes, wal_buffers_full
  FROM pg_stat_wal;

-- WAL write / fsync counts per process type moved to pg_stat_io in 18
SELECT backend_type, context, writes, fsyncs, write_time, fsync_time
  FROM pg_stat_io
 WHERE object = 'wal'
 ORDER BY writes DESC;

In PostgreSQL 18 the old wal_write, wal_sync, wal_write_time and wal_sync_time columns were removed from pg_stat_wal; use pg_stat_io (timings need track_wal_io_timing). If client backend has many WAL fsyncs and walwriter few, commits are synchronous and small, which is normal.

Choosing synchronous_commit

ValueCOMMIT waits forLost on crash
on (default)local flush (+ sync standby flush if configured)nothing
locallocal flush onlynothing locally
offnothingup to ~3 × wal_writer_delay of commits

synchronous_commit can be set per transaction (SET LOCAL synchronous_commit = off) for unimportant writes such as session logs, while money transfers stay safe. Unlike fsync = off, it never risks corruption.

Process facts

One process, postgres: walwriter in ps, backend_type = 'walwriter'. Started by the postmaster after recovery ends, so it does not run on a standby. Killing it with signal 9 causes a crash restart: LOG: WAL writer process (PID 3151) was terminated by signal 9: Killed.

For developers: inside the source

Where it lives

  • walwriter.c: WalWriterMain(), a short loop around XLogBackgroundFlush().
  • xlog.c: everything else: XLogBackgroundFlush(), XLogWrite(), XLogFlush(), XLogSetAsyncXactLSN(), AdvanceXLInsertBuffer().
  • xact.c: RecordTransactionCommit() decides between a synchronous XLogFlush() and the asynchronous path.

Key state in XLogCtlData

  • LogwrtRqst (Write/Flush requests) and asyncXactLSN, the newest async commit, under the spinlock info_lck.
  • logInsertResult, logWriteResult, logFlushResult: pg_atomic_uint64 positions read with RefreshXLogWriteResult().
  • WalWriterSleeping: true while the WAL writer hibernates.
  • WALWriteLock: the LWLock any process must hold to call XLogWrite().

Call flow

An asynchronous commit: RecordTransactionCommit() → XLogSetAsyncXactLSN(XactLastRecEnd), which advances asyncXactLSN and sets the WAL writer's latch (ProcGlobal->walwriterProc) if it is sleeping or if wal_writer_flush_after blocks are unflushed. Each WAL writer round:

  1. WalWriterMain() calls XLogBackgroundFlush().
  2. Target = LogwrtRqst.Write rounded down to a page; if already flushed, use asyncXactLSN and set flexible = false.
  3. Decide whether to also fsync (time since lastflush ≥ WalWriterDelay, or ≥ WalWriterFlushAfter blocks).
  4. WaitXLogInsertionsToFinish(), take WALWriteLock, XLogWrite(), release.
  5. WalSndWakeupProcessRequests() wakes walsenders, then AdvanceXLInsertBuffer(InvalidXLogRecPtr, …, true) pre-initialises free WAL buffer pages, taking work off backends' insert path.

If it returns false for LOOPS_UNTIL_HIBERNATE (50) rounds, the loop calls SetWalWriterSleeping(true) and waits WalWriterDelay * HIBERNATE_FACTOR (25) with WAIT_EVENT_WAL_WRITER_MAIN.

Compare a synchronous commit: XLogFlush() uses LWLockAcquireOrWait(WALWriteLock, …), so concurrent committers wait for one flusher instead of queuing to flush again (group commit); commit_delay adds a pg_usleep() there.

Experimenting

  • wal_debug exists only in builds compiled with WAL_DEBUG; then XLogBackgroundFlush() logs xlog bg flush request write …; flush: ….
  • pg_stat_io with object = 'wal' and backend_type = 'walwriter'; pg_walinspect to look at the records themselves.
  • The WAL section of README explains the write-ahead rule and the insert/write/flush positions.

Parameters that shape this

wal_writer_delay wal_writer_flush_after synchronous_commit wal_buffers commit_delay wal_sync_method track_wal_io_timing

Related topics

Write-ahead log (WAL) Synchronous replication Checkpointer Background writer Crash recovery Postmaster