Background processes
Background writer
The background writer wakes up every bgwriter_delay, guesses how many buffers backends will need soon, and writes dirty ones ahead of time so that queries rarely have to write pages themselves.
In plain words
Shared buffers are a fixed number of page slots. When a query needs a page that is not cached, its backend must free a slot: it walks around the buffers like the hand of a clock, skipping recently used pages, and takes the first one that nobody needs. If that page is dirty (changed but not yet saved), the backend must write it to disk first, and your query waits.
The background writer exists to avoid that wait. It wakes up regularly (every bgwriter_delay, 200 ms by default), looks at how many buffers backends took recently, and cleans about that many (times bgwriter_lru_multiplier) just ahead of the clock hand. When the backend arrives there, it finds clean slots.
It is capped by bgwriter_lru_maxpages per round so it never floods the disk. When the server is idle it hibernates and is woken up by the next backend that needs a buffer.
Try the sliders: raise the load or lower the cap and watch the red "backend had to write" squares appear.
For DBAs and operators
One round of BgBufferSync()
- Read how many buffers were allocated since the last round (
recent_alloc) and where the clock hand is. - Smooth it: if the new value is higher it is taken at once, otherwise the average decays over 16 rounds.
upcoming = smoothed_alloc × bgwriter_lru_multiplier, but at least a minimum so that the whole pool is scanned at least once every 2 minutes.- Scan forward from where the last round stopped (or from the clock hand if the hand has overtaken it). A buffer with
usage_count = 0and no pins is "reusable"; if it is dirty, write it. Stop when enough reusable buffers are ahead of the hand, or whenbgwriter_lru_maxpagespages were written; the latter incrementsmaxwritten_clean.
It does not decrement usage counts (only the backends' clock sweep does) and never writes recently used pages: those are written by the checkpointer, or by a backend when it finally evicts them. It also asks the kernel to start writing its data out every bgwriter_flush_after (512kB) on Linux, to avoid big write bursts later. With wal_level replica or higher it also logs a running-transactions snapshot into WAL every 15 seconds, which helps standbys and logical decoding get started.
Hibernation
If a round finds nothing to do and no buffers were allocated, it sleeps for bgwriter_delay × 50 instead (wait_event = BgwriterHibernate, otherwise BgwriterMain). The first backend that allocates a buffer wakes it up.
Monitoring
SELECT buffers_clean, maxwritten_clean, buffers_alloc, stats_reset
FROM pg_stat_bgwriter;
-- who writes buffers? (PostgreSQL 16+)
SELECT backend_type, context, writes, evictions
FROM pg_stat_io
WHERE object = 'relation' AND writes > 0;Since PostgreSQL 17 pg_stat_bgwriter has only these columns; checkpoint counters moved to pg_stat_checkpointer and the old buffers_backend / buffers_backend_fsync moved to pg_stat_io (rows for client backend).
Tuning
maxwritten_cleangrowing steadily means the cap stopped the writer often: raisebgwriter_lru_maxpages(for example 400 to 1000) or the multiplier.- Many backend writes in
pg_stat_io(contextnormal) with a busy, write-heavy workload: same advice, or lowerbgwriter_delay(for example 50ms). - An aggressive writer may write the same hot page several times; that is extra I/O. Setting
bgwriter_lru_maxpages = 0disables background writing (the process still runs). - Backend writes in context
bulkread/bulkwrite/vacuumcome from ring buffers and are not something the background writer can avoid.
Process facts
One process, postgres: background writer in ps, backend_type = 'background writer'. It starts with the postmaster (also during recovery on a standby). If killed with signal 9 the postmaster performs a crash restart: LOG: background writer process (PID 6069) was terminated by signal 9: Killed.
For developers: inside the source
Where it lives
bgwriter.c:BackgroundWriterMain(), the sleep/hibernate logic and the standby snapshot logging.bufmgr.c:BgBufferSync(), the algorithm the simulation shows, andSyncOneBuffer().freelist.c: the clock sweep (StrategyGetBuffer()),StrategySyncStart()andStrategyNotifyBgWriter().README: buffer replacement strategy and buffer header locking.
Shared state
BufferStrategyControl (freelist.c) holds the clock hand nextVictimBuffer (a pg_atomic_uint32), completePasses, the allocation counter numBufferAllocs and bgwprocno, all read together under the spinlock buffer_strategy_lock. Each round, StrategySyncStart(&complete_passes, &num_buf_alloc) returns the hand position and resets the allocation count; that is the recent_alloc fed into the estimate.
One round
BackgroundWriterMain()callscan_hibernate = BgBufferSync(&wb_context), thenpgstat_report_bgwriter()andpgstat_report_wal(true).BgBufferSync()compares its own position with the hand (next_to_clean,next_passes), estimates reusable buffers ahead from a smoothed density, and updatessmoothed_allocwith fast-attack/slow-decline:if (smoothed_alloc <= (float) recent_alloc) smoothed_alloc = recent_alloc; else smoothed_alloc += ((float) recent_alloc - smoothed_alloc) / smoothing_samples; /* 16 */- The scan target is at least
min_scan_buffers = NBuffers / (scan_whole_pool_milliseconds / BgWriterDelay), withscan_whole_pool_milliseconds = 120000. - The loop calls
SyncOneBuffer(next_to_clean, true, wb_context). Withskip_recently_used = truea pinned buffer or one with non-zero usage count is skipped; a clean one returnsBUF_REUSABLE, a written oneBUF_WRITTEN. Hittingbgwriter_lru_maxpagesincrementsmaxwritten_clean. - Writes are queued in a
WritebackContext;bgwriter_flush_aftercontrols whensync_file_range()-style writeback hints are issued.
Sleeping
It normally waits BgWriterDelay ms (WAIT_EVENT_BGWRITER_MAIN). If BgBufferSync() returned true, it registers itself with StrategyNotifyBgWriter(MyProcNumber) and sleeps BgWriterDelay * HIBERNATE_FACTOR (50) with WAIT_EVENT_BGWRITER_HIBERNATE; the next StrategyGetBuffer() sets its latch. Separately, every LOG_SNAPSHOT_INTERVAL_MS (15000) it calls LogStandbySnapshot() if WAL has advanced since the last one.
Experimenting
- Compile with
-DBGW_DEBUGto get aDEBUG1line per round fromBgBufferSync()(recent_alloc, smoothed, density, wrote …). pg_buffercache_summary()/pg_buffercache_usage_counts()show dirty buffers and usage-count distribution;pg_stat_iorows forbackground writercount its writes.
Parameters that shape this
bgwriter_delay bgwriter_lru_maxpages bgwriter_lru_multiplier bgwriter_flush_after shared_buffers
Related topics
Shared buffers Checkpointer Checkpoints Postmaster I/O workers Memory architecture