Crash a disposable PostgreSQL instance deliberately, observe REDO and transaction outcomes on restart, and distinguish crash recovery from media corruption, checksum detection, and backup recovery.

Crash Recovery Timeline, Recovery Consistency, and Corruption Boundaries

Use a separate disposable PostgreSQL instance to observe crash REDO and transaction visibility, then draw a hard boundary between crash recovery and media-corruption recovery.

Intermediate → Advanced180–240 minutesWAL/durability observability labCurrent patched PostgreSQL 18.xCore PostgreSQL only; host utilities used where explicitly labeledOwner connection for SQL labs; pg_monitor/superuser only where statedCrash injection uses a separate disposable local cluster on port 55433No managed-service or paid dependencyLast reviewed: August 2026

Learning outcomes

A clean shutdown is not the failure mode WAL was invented to solve. In this lesson you will create a completely disposable second PostgreSQL cluster, commit one ServiceHub event, leave a second transaction uncommitted, terminate the server with immediate shutdown, and restart it. The goal is to see the recovery boundary directly without risking the Chapter 01 lab.

01

Run an unclean-stop experiment on a separate local PostgreSQL data directory and port.

02

Verify that durable committed work survives while an uncommitted transaction does not become committed after recovery.

03

Read startup/recovery log evidence without depending on one exact message string.

04

Distinguish crash recovery from torn-page protection, checksums, media corruption, and backup/PITR recovery.

05

Explain why pg_resetwal is not an ordinary corruption-repair tool.

Mandatory safety rule

Do not run this lab against servicehub_lab, a shared developer database, or any production/replica data directory. Create a fresh data directory on port 55433 and delete it when finished. The use of trust authentication below is acceptable only for a disposable, loopback-only teaching cluster on a single-user machine.

1. Create a disposable crash-recovery cluster

The commands differ only in shell syntax. Use PostgreSQL 18 server utilities from the same installation. The explicit alternate port prevents accidental connection to the Chapter 01 instance.

shell · Linux/macOS shell: initialize and start
export PG12_DATA="$HOME/pg12_crash_lab"rm -rf "$PG12_DATA"initdb -D "$PG12_DATA" -A trustpg_ctl -D "$PG12_DATA" -l "$PG12_DATA/server.log" -o "-p 55433 -c listen_addresses=localhost" startcreatedb -p 55433 servicehub_wal_crash
powershell · Windows PowerShell: initialize and start
$env:PG12_DATA = Join-Path $HOME "pg12_crash_lab"if (Test-Path $env:PG12_DATA) { Remove-Item -Recurse -Force $env:PG12_DATA }initdb -D $env:PG12_DATA -A trustpg_ctl -D $env:PG12_DATA -l "$env:PG12_DATA\server.log" -o "-p 55433 -c listen_addresses=localhost" startcreatedb -p 55433 servicehub_wal_crash

PostgreSQL 18 initdb enables data checksums by default unless explicitly disabled. Verify rather than assume the cluster's exact setting.

shell · verify the disposable target
psql -p 55433 -d servicehub_wal_crash -c "SELECT version(), current_database(), pg_is_in_recovery();"

2. Create a table and one durable committed row

sql · create crash-test state
CREATE TABLE public.recovery_probe (    probe_id integer PRIMARY KEY,    note text NOT NULL,    created_at timestamptz NOT NULL DEFAULT clock_timestamp());BEGIN;INSERT INTO public.recovery_probe (probe_id, note)VALUES (1, 'committed-before-immediate-stop');COMMIT;SELECT probe_id, note FROM public.recovery_probe ORDER BY probe_id;

Row 1 is the positive control: it was committed under the cluster's ordinary durable settings before the crash simulation.

3. Leave a second transaction uncommitted

Open a second psql session and deliberately leave the transaction open. Do not close this session normally.

sql · Session B: open transaction and stop before COMMIT
BEGIN;INSERT INTO public.recovery_probe (probe_id, note)VALUES (2, 'uncommitted-at-immediate-stop');SELECT probe_id, note FROM public.recovery_probe ORDER BY probe_id;-- Keep this transaction open. Do not COMMIT or ROLLBACK.

Session B can see its own uncommitted row. Another session should not see it under the default Read Committed isolation level. That is MVCC visibility, not yet crash recovery.

sql · Session A: verify the uncommitted row is invisible
SELECT probe_id, noteFROM public.recovery_probeORDER BY probe_id;

4. Stop immediately and restart

From a third shell, use immediate shutdown. PostgreSQL terminates server processes without a clean checkpoint. The next startup must perform crash recovery.

shell · simulate an unclean server stop
pg_ctl -D "$PG12_DATA" -m immediate stoppg_ctl -D "$PG12_DATA" -l "$PG12_DATA/server.log" -o "-p 55433 -c listen_addresses=localhost" start
powershell · PowerShell equivalent
pg_ctl -D $env:PG12_DATA -m immediate stoppg_ctl -D $env:PG12_DATA -l "$env:PG12_DATA\server.log" -o "-p 55433 -c listen_addresses=localhost" start

Expected log evidence includes an indication that the previous shutdown was interrupted/not clean, followed by REDO/recovery activity and eventually a message that the database system is ready to accept connections. Exact wording, LSNs, and timestamps are not stable output contracts.

During startup recovery PostgreSQL begins from the redo position associated with the last usable checkpoint and replays later WAL records needed to bring data files forward. It also reconstructs transaction commit/abort visibility. Recovery is therefore not “re-run the SQL statements”; it is application of WAL records to physical database state plus transaction-status rules.

A checkpoint is not a promise that every transaction after it was lost or that every page after it was dirty. It is a recovery starting boundary. The final recovered state is determined by WAL records and transaction outcomes up to the available end of WAL.

5. Verify business state after recovery

sql · verify committed and uncommitted outcomes
SELECT pg_is_in_recovery() AS in_recovery;SELECT probe_id, noteFROM public.recovery_probeORDER BY probe_id;

After startup has completed, pg_is_in_recovery() should be false. Row 1 should exist. Row 2 should not be committed. Recovery may physically replay WAL records related to aborted/incomplete work while still reconstructing transaction visibility so the uncommitted row is not part of the committed database state.

6. Read recovery logs as a timeline

shell · Linux/macOS: inspect recent server log lines
tail -n 80 "$PG12_DATA/server.log"
powershell · PowerShell: inspect recent server log lines
Get-Content "$env:PG12_DATA\server.log" -Tail 80

Look for the sequence: abnormal prior shutdown → recovery/REDO start → REDO completion/end-of-WAL handling → readiness. Do not build monitoring that parses one exact English sentence if a structured or supported metric is available.

7. Crash recovery is not arbitrary corruption repair

WAL replays valid recorded changes after a crash. Full-page writes reduce the risk that an interrupted operating-system page write leaves a torn page that row-level WAL cannot reconstruct. Data checksums can detect many forms of on-disk page corruption, but detection does not manufacture a clean replacement page. A failed disk, overwritten relation file, missing WAL history, or corrupted backup crosses into restore/recovery engineering.

Failure What WAL/crash recovery can do What it cannot promise
PostgreSQL/OS stops after durable commits Replay required WAL to a consistent state Guarantee zero downtime
Torn page during crash Full-page image can restore the page when available Repair arbitrary later media corruption
Checksum mismatch Help detect a corrupted page Generate the correct missing page contents
Lost/corrupted data file May participate in restore when valid base backup/WAL exist Recreate the whole cluster from current pg_wal alone
Dangerous misconception

pg_resetwal is a last-resort utility for damaged control/WAL state when the server will not start and recovery from a backup is not possible. Its own documentation warns that the database can contain inconsistent data afterward. It is not a routine “fix corruption” command and is intentionally excluded from this lab.

8. Cleanup the disposable cluster

shell · Linux/macOS cleanup
pg_ctl -D "$PG12_DATA" stoprm -rf "$PG12_DATA"
powershell · PowerShell cleanup
pg_ctl -D $env:PG12_DATA stopRemove-Item -Recurse -Force $env:PG12_DATA

Production crash testing should use infrastructure designed for failure injection, with backups, observability, fencing, and clear rollback procedures. Never use process-kill experiments as a substitute for verified storage-failure testing.

Check your understanding

  1. Why use a separate data directory and port for the crash lab?
  2. Why does row 1 survive while row 2 does not become committed?
  3. What does pg_is_in_recovery() tell you after startup is complete?
  4. What problem do full-page writes address?
  5. Why is a checksum error not automatically repaired by WAL?
Review the answers

The separate cluster confines destructive failure injection. Row 1 has a durable commit record; row 2 never committed, so recovery reconstructs a consistent transaction state without making it visible as committed. After normal startup completes, pg_is_in_recovery() is false. Full-page writes protect against torn/incomplete page writes around crashes. Checksums detect corruption; WAL does not guarantee a valid replacement for arbitrary media damage.

Authoritative references

Durability behavior depends on PostgreSQL version, storage semantics, configuration source, replication topology, and the exact failure mode. The references below are the primary source for the mechanisms used in this lesson.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.