DB Internals AtlasPostgreSQL 18.6 Home About

Background processes

Archiver

With archive_mode on, the archiver process copies every completed WAL segment somewhere safe using archive_command or archive_library. A segment may only be recycled after it was archived, so a failing command makes pg_wal grow.

In plain words

WAL is written in 16 MB files called segments. Point-in-time recovery needs a copy of every segment, kept outside the server. The archiver is the small process that makes those copies.

When a segment is full, PostgreSQL drops a marker file <segment>.ready into pg_wal/archive_status/ and pokes the archiver. The archiver runs your archive_command (for example a cp, or a backup tool such as pgBackRest) for the oldest ready segment. Exit code 0 means success: the marker is renamed to .done and the checkpointer may recycle the segment later.

If the command fails, the archiver tries again, and again, forever. It never skips a segment. Meanwhile new segments keep arriving and none can be removed, so pg_wal grows until the disk is full. Switch on the failure toggle and watch it happen.

For DBAs and operators

The work loop

  1. Wake on its latch (set when a segment is marked .ready), or after 60 seconds at the latest.
  2. Scan pg_wal/archive_status/ for .ready files, oldest first (it remembers up to 64 candidates per directory scan instead of rescanning for each file; timeline history files go first).
  3. For each: run archive_command with %p (path relative to the data directory) and %f (file name), or call the archive_library module's callback (PostgreSQL 15+; basic_archive is shipped as an example).
  4. Success: rename to .done, update pg_stat_archiver. Failure: retry after 1 second; after 3 failures in a row give up for now and sleep (60 s or until woken by the next segment).

Real log lines from a failing command:

LOG: archive command failed with exit code 1DETAIL: The failed archive command was: test ! -f /archive/000000010000000000000012 && cp pg_wal/000000010000000000000012 /archive/000000010000000000000012WARNING: archiving write-ahead log file "000000010000000000000012" failed too many times, will try again later

How to see it

One process, started by the postmaster when archive_mode is on (or always, which also archives on a standby). ps shows its last result in the title: postgres: archiver last was 000000010000000000000011 or postgres: archiver failed on 000000010000000000000012. backend_type = 'archiver', wait_event ArchiverMain while idle, ArchiveCommand while the command runs.

SELECT archived_count, last_archived_wal, last_archived_time,
       failed_count, last_failed_wal, last_failed_time
  FROM pg_stat_archiver;

-- how many segments are waiting?
SELECT count(*) FROM pg_ls_archive_statusdir()
 WHERE name LIKE '%.ready';

If last_failed_time is newer than last_archived_time, archiving is currently stuck.

Rules for archive_command

  • Return 0 only when the file is safely stored (fsynced). Returning 0 too early loses WAL silently.
  • Refuse to overwrite an existing different file (test ! -f), but returning success for an identical copy is fine and helps after a crash between copy and rename.
  • It runs serially, one segment at a time: a slow command (high latency to S3, compression) becomes a backlog under heavy write load. Tools such as pgBackRest offer asynchronous archiving with parallel pushes.
  • Changing archive_command needs only a reload; archive_mode needs a restart. Setting both archive_command and archive_library is an error since PostgreSQL 16.
  • archive_timeout can force a segment switch on quiet systems so completed WAL becomes archivable sooner. Each switch still produces a complete WAL segment file: 16 MB by default, or whatever wal_segment_size was chosen when the cluster was initialized.

Typical support case: pg_wal is full

Check pg_stat_archiver and the server log first. Fix the cause (permissions, full archive volume, expired credentials, missing directory) and the backlog drains by itself. Never delete files from pg_wal by hand. When the disk does fill up, the server stops with a PANIC such as PANIC: could not write to file "pg_wal/xlogtemp.1234": No space left on device Free space elsewhere or extend the volume, then start it again. Other WAL retainers to check: replication slots (pg_replication_slots.wal_status) and wal_keep_size.

If it dies

If the archive command is killed by a signal, the archiver exits with a FATAL error, for example FATAL: archive command was terminated by signal 9: Killed (or FATAL: archive command failed with exit code 137 when the shell reports the signal), and the postmaster starts a new one; nothing is lost because the .ready files are still there. If the archiver process itself is killed with SIGKILL, the postmaster performs a crash restart of the whole server.

For developers: inside the source

Where it lives

  • pgarch.c: PgArchiverMain(), pgarch_MainLoop(), pgarch_ArchiverCopyLoop(), pgarch_readyXlog().
  • xlogarchive.c: the producer side: XLogArchiveNotify() (creates .ready, calls PgArchWakeup()), XLogArchiveCheckDone(), XLogArchiveCleanup().
  • shell_archive.c: the built-in module behind archive_command.
  • archive_module.h: the archive module API.

Producer and consumer

When XLogWrite() finishes a segment it calls XLogArchiveNotifySeg(), which writes archive_status/<seg>.ready and sets the archiver's latch through PgArch->pgprocno. At checkpoint time RemoveOldXlogFiles() only removes or recycles a segment when XLogArchiveCheckDone() returns true. PgArchData in shared memory has just two fields: pgprocno and the atomic force_dir_scan (set by PgArchForceDirScan()).

Archiver call flow

  1. pgarch_MainLoop() waits on its latch with timeout PGARCH_AUTOWAKE_INTERVAL (60 s), WAIT_EVENT_ARCHIVER_MAIN.
  2. pgarch_ArchiverCopyLoop() repeatedly calls pgarch_readyXlog(). That scans the directory into a binary heap ordered by ready_file_comparator() (timeline history files first, then oldest) and keeps up to NUM_FILES_PER_DIRECTORY_SCAN (64) names, so it does not rescan for every file.
  3. pgarch_archiveXlog() sets the ps title and calls the module's archive_file_cb; for archive_command that is shell_archive_file(), which runs the command inside WAIT_EVENT_ARCHIVE_COMMAND.
  4. Success: pgarch_archiveDone() renames .ready → .done, pgstat_report_archiver(xlog, false). Failure: pgstat_report_archiver(xlog, true), pg_usleep(1000000L), and after NUM_ARCHIVE_RETRIES (3) the "failed too many times" WARNING and back to the main loop.

In shell_archive_file() the error level is wait_result_is_any_signal(rc, true) ? FATAL : LOG, which is why a command killed by a signal takes the archiver down. The postmaster then calls HandleChildCrash() only if the archiver exited with a status other than 0 or 1 (a FATAL exit is 1), and restarts it after PGARCH_RESTART_INTERVAL (10 s, see PgArchCanRestart()).

Writing an archive module

A library named in archive_library exports _PG_archive_module_init(), returning an ArchiveModuleCallbacks with startup_cb, check_configured_cb, archive_file_cb (required) and shutdown_cb; per-module state goes in ArchiveModuleState->private_data. contrib/basic_archive is the reference implementation (it fsyncs and renames atomically).

Experimenting

Parameters that shape this

archive_mode archive_command archive_library archive_timeout wal_level max_wal_size wal_keep_size

Related topics

Archiving and point-in-time recovery Write-ahead log (WAL) Checkpointer Checkpoints Postmaster Logger (logging collector) WAL sender WAL summarizer