Skip to main content
RunBook Academy

PostgreSQLXVII · Capacity, Maintenance and UpgradesMaintenance

Planned maintenance and its resource cost

Intermediate⏱ ~30 minpsql

What you'll learn

  • Price a maintenance operation in I/O, locks, WAL and space
  • Choose between the blocking and concurrent forms
  • Schedule maintenance so it can be abandoned safely
  • Recognise operations that cannot be interrupted cheaply

Prerequisites

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

Maintenance is not free, and each operation spends a different currency. Scheduling it well means knowing which.

The four currencies

OperationI/OLocksWALSpace
VACUUMModerateSHARE UPDATE EXCLUSIVELowNone
VACUUM FULLFull rewriteACCESS EXCLUSIVEFull rewriteSecond copy
ANALYZESampled readSHARE UPDATE EXCLUSIVEMinimalNone
REINDEXRebuildACCESS EXCLUSIVEFull indexNew index
REINDEX CONCURRENTLYRebuild ×2 passesSHARE UPDATE EXCLUSIVEFull indexBoth indexes
CREATE INDEXFull scanSHARE — blocks writesFull indexNew index
CREATE INDEX CONCURRENTLYTwo scansSHARE UPDATE EXCLUSIVEFull indexNew index
Anti-wraparound freezeFull scanSHARE UPDATE EXCLUSIVEVery highNone

The bolded cells are what makes an operation unschedulable during business hours.

What the locks actually cost

Destructivethe locks VACUUM FULL holds while running
$ SELECT l.pid, l.mode, l.granted, c.relname FROM pg_locks l
JOIN pg_class c ON c.oid=l.relation WHERE c.relname LIKE 'big_bloat%';
 pid | mode                | granted | relname
-----+---------------------+---------+----------------
245 | AccessExclusiveLock | t       | big_bloat_pkey
245 | ShareLock           | t       | big_bloat
245 | AccessExclusiveLock | t       | big_bloat
247 | ShareLock           | t       | big_bloat
247 | AccessExclusiveLock | t       | big_bloat_pkey
(5 rows)

Lesson VIII-06 measured what that does to ordinary traffic: a plain SELECT count(*) issued one second later, with lock_timeout = 4s, timed out. ACCESS EXCLUSIVE conflicts with everything, including reads.

And a lock request queues everything behind it, which lesson IX-05 measured directly: a plain SELECT was refused after 6 seconds while the only granted lock was another plain SELECT — the queue, not the holder.

WAL is a currency people forget

Read-only / Safefreezing a small table
$ -- WAL generated by the freeze
445 full page images, 3.6 MB of WAL to freeze a 3.5 MB table

More WAL than the table. Every frozen page is a full page image, as lesson XII-01 established. On a terabyte cluster reaching wraparound age, that is a terabyte of WAL — which must flow through pg_wal, into the archive, and to every standby, with lesson XVII-02’s consequences if any of them cannot absorb it.

A VACUUM FULL of a large table has the same shape: it rewrites every page, so it generates WAL proportional to the table.

Ordering

When several operations are needed, the order changes the total cost:

  1. VACUUM first — it may make the rest unnecessary.
  2. ANALYZE after any large data change, so the planner is not working from stale statistics.
  3. REINDEX only where bloat is measured, not on a schedule. Lesson VIII-06 measured a table with zero dead tuples and 49.96% free space — bloat is not dead tuples.
  4. VACUUM FULL last, and rarely. It is the only one that returns space unconditionally, and the most expensive way to do it.

What to take from this

  • Four currencies: I/O, locks, WAL, space. Each operation spends a different mix.
  • ACCESS EXCLUSIVE conflicts with reads. Measured: a plain SELECT timed out after 4 s against a VACUUM FULL.
  • Concurrent forms trade blocking for two passes, waiting on old transactions, double space, and invalid indexes on failure.
  • WAL is a real cost: 3.6 MB to freeze a 3.5 MB table.
  • Ask what happens if you abandon the operation, not just how long it takes. Use the pg_stat_progress_* views.
  • VACUUMANALYZEREINDEX where measured → VACUUM FULL rarely.

Cross-course references

  • Linux for Production Sysadmins — Part LXIV (Rolling maintenance) covers sizing a window from a measurement, and Part LXXIII (Change management) covers recording what it will cost before it is approved.
  • Ansible for Production Sysadmins — Part XLVIII (Maintenance windows and rollback) and Part XXX (Host targeting and blast radius) cover running the work across an estate without doing all of it at once.

Quiz

Knowledge check · 6 questions

  1. Q1. A CREATE INDEX CONCURRENTLY is cancelled after 40 minutes. What is left behind?

  2. Q2. Why is 'what happens if I abandon this at minute 40' a better scheduling question than 'how long will it take'?

  3. Q3. A 3.5 MB table was frozen by an anti-wraparound vacuum and generated 3.6 MB of WAL. Why more WAL than table?

  4. Q4. Which alternatives should be considered before running VACUUM FULL? Select all that apply.

  5. Q5. A VACUUM FULL blocks only writes, so read-only reporting queries continue during the rewrite.

  6. Q6. Describe the four currencies a maintenance operation spends, and give an example of an operation dominated by each.

Passing score: 75%. Answers are checked in this browser.