PostgreSQLXVII · Capacity, Maintenance and UpgradesMaintenance
Capacity planning beyond current database size
What you'll learn
- Enumerate every consumer of space on a PostgreSQL host
- Project growth from measurement rather than from row counts
- Size for the operations that need headroom, not for steady state
- Plan connections, memory and I/O alongside storage
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
“The database is 400 GB, so we need 800 GB” is the plan that produces a 3 a.m. page. Here is everything that plan omits.
What consumes space
| Consumer | Sized by | Measured in this course |
|---|---|---|
| Heap and indexes | Rows and indexing | 65 MB of indexes over a 33 MB heap, lesson X-05 |
pg_wal | Write rate and retention | 4,359 kB/s ≈ 367 GB/day, lesson XII-02 |
| Bloat headroom | Churn and vacuum timing | 1.7 MB → 19 MB from one held snapshot, lesson VII-05 |
| Temp files | work_mem and query shape | Lesson XI-05 |
| Logs | Logging configuration | Lesson XVI-01 |
| Backups, if local | Retention and size | Lesson XIII-04 |
| The archive, if local | WAL rate × retention | Lesson XIII-05 |
| Operational headroom | The largest single operation | See below |
The two in bold are the ones that surprise people, and both have been measured earlier in this course rather than estimated.
Projecting growth from measurement
Row counts are the wrong unit. Measure the bytes.
-- record this weekly; the derivative is the growth rate
SELECT current_database() AS db,
now() AS at,
pg_database_size(current_database()) AS db_bytes,
(SELECT sum(pg_total_relation_size(oid))
FROM pg_class WHERE relkind IN ('r','m')) AS rel_bytes;
-- the biggest objects, and how much of each is index
SELECT relname,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total,
pg_size_pretty(pg_relation_size(c.oid)) AS heap,
pg_size_pretty(pg_indexes_size(c.oid)) AS indexes,
round(100.0*pg_indexes_size(c.oid)
/NULLIF(pg_total_relation_size(c.oid),0)) AS pct_index
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog','information_schema')
ORDER BY pg_total_relation_size(c.oid) DESC LIMIT 20;
The pct_index column earns its place. Lesson X-05 measured a table
with 65 MB of indexes over a 33 MB heap — the indexes were twice the
data — and index growth tracks write volume rather than row count.
The other resources
Connections. Lesson IV-02’s arithmetic: max_connections reserves
per-connection memory whether or not connections exist, and the practical
limit is usually well below what people set. Pooling is the answer, not
a larger number.
Memory. Lesson XI-01’s map. shared_buffers plus max_connections × work_mem in the worst case plus maintenance_work_mem per autovacuum
worker, and lesson XI-06’s OOM killer for what happens when the sum
exceeds reality.
I/O. Lesson VI-01 measured 854 µs fdatasync predicting a ~1,170 tps
commit ceiling, and pgbench then measured 842. Commit rate is bounded by
fsync latency, and no amount of CPU changes that.
CPU. Usually the last constraint to bind, and the easiest to see.
What to take from this
- The database size is the smallest term. WAL, archive, backups, logs, temp and bloat headroom are the rest.
- Measured: 4,359 kB/s of WAL is 367 GB a day, none of it database size.
- Size for the largest single operation — a table rewrite, a
restore, a copy-mode
pg_upgrade. - Project from measured bytes over time, not from row counts.
- Indexes can exceed the heap; measured at twice its size.
- Growth is not linear: index fragmentation, step-function bloat, and conditional truncation.
- Plan connections, memory and fsync latency alongside storage.
Cross-course references
- Linux for Production Sysadmins — Part LXVI (Capacity planning for clusters) covers forecasting from a trend rather than from a current reading, and Part XVI (LVM) covers whether growing the volume is actually available to you.
- Ceph & Distributed Storage — Part LXIII (Capacity management) and Part LXVI (Capacity forecasting) cover the same arithmetic where adding capacity has a rebalance cost.
- Observability for Production Sysadmins — Part LXXIV (Capacity planning) covers holding the series long enough for a forecast to mean anything.
Quiz
Knowledge check · 6 questions
Q1. A 400 GB database is given an 800 GB volume holding data, pg_wal, the archive and local backups. WAL is generated at roughly 4,000 kB/s. What happens first?
Q2. Why must a cluster be sized for the largest single operation rather than for steady state?
Q3. A table's size has been flat for months and then jumps by an order of magnitude in a week, with no change in row count. What is the most likely explanation?
Q4. Which consume host storage on a PostgreSQL server beyond the heap and indexes? Select all that apply.
Q5. Vacuum reliably returns space to the filesystem when rows are deleted.
Q6. Why should capacity planning track derivatives and thresholds rather than absolute values?
Passing score: 75%. Answers are checked in this browser.