PostgreSQLXVII · Capacity, Maintenance and UpgradesMaintenance
Planned maintenance and its resource cost
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
Maintenance is not free, and each operation spends a different currency. Scheduling it well means knowing which.
The four currencies
| Operation | I/O | Locks | WAL | Space |
|---|---|---|---|---|
VACUUM | Moderate | SHARE UPDATE EXCLUSIVE | Low | None |
VACUUM FULL | Full rewrite | ACCESS EXCLUSIVE | Full rewrite | Second copy |
ANALYZE | Sampled read | SHARE UPDATE EXCLUSIVE | Minimal | None |
REINDEX | Rebuild | ACCESS EXCLUSIVE | Full index | New index |
REINDEX CONCURRENTLY | Rebuild ×2 passes | SHARE UPDATE EXCLUSIVE | Full index | Both indexes |
CREATE INDEX | Full scan | SHARE — blocks writes | Full index | New index |
CREATE INDEX CONCURRENTLY | Two scans | SHARE UPDATE EXCLUSIVE | Full index | New index |
| Anti-wraparound freeze | Full scan | SHARE UPDATE EXCLUSIVE | Very high | None |
The bolded cells are what makes an operation unschedulable during business hours.
What the locks actually cost
$ 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
$ -- WAL generated by the freeze445 full page images, 3.6 MB of WAL to freeze a 3.5 MB tableMore 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:
VACUUMfirst — it may make the rest unnecessary.ANALYZEafter any large data change, so the planner is not working from stale statistics.REINDEXonly 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.VACUUM FULLlast, 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 EXCLUSIVEconflicts with reads. Measured: a plainSELECTtimed out after 4 s against aVACUUM 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. VACUUM→ANALYZE→REINDEXwhere measured →VACUUM FULLrarely.
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
Q1. A CREATE INDEX CONCURRENTLY is cancelled after 40 minutes. What is left behind?
Q2. Why is 'what happens if I abandon this at minute 40' a better scheduling question than 'how long will it take'?
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?
Q4. Which alternatives should be considered before running VACUUM FULL? Select all that apply.
Q5. A VACUUM FULL blocks only writes, so read-only reporting queries continue during the rewrite.
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.