Vacuum & bloatPro · lab evidence1 real lab transcript

Online VACUUM, REINDEX, and Table Rewrites

Over time tables and indexes accumulate bloat — dead tuples and empty space left by MVCC. There are two fundamentally different ways to deal with it: reclaiming space for reuse within the object (online, cheap) versus physically shrinking the object and returning space to the OS (expensive, traditionally blocking). Choosing the wrong one causes outages.

The one thing to understand first

Over time tables and indexes accumulate bloat — dead tuples and empty space left by MVCC. There are two fundamentally different ways to deal with it: reclaiming space for reuse within the object (online, cheap) versus physically shrinking the object and returning space to the OS (expensive, traditionally blocking). Choosing the wrong one causes outages.

*Routine VACUUM reclaims space for reuse without shrinking the file; returning space to the OS is a separate, far more expensive operation — and there is an online tool for every job, so you should never need the blocking one.* Match the tool to the goal and bloat cleanup is invisible to users.

Plain VACUUM: online, but doesn't shrink files

Regular VACUUM (and autovacuum) marks dead tuple space reusable and updates the free space and visibility maps. It runs with a weak lock that allows concurrent reads and writes. What it does not do is return space to the operating system — the file stays the same size, with free space inside it for future rows. For steady-state churn this is exactly what you want, and it should be handled automatically by a well-tuned autovacuum.

VACUUM FULL: shrinks, but locks everything

VACUUM FULL rewrites the entire table into a new compact file and swaps it in, returning space to the OS. The catch: it holds an ACCESS EXCLUSIVE lock for the whole rewrite — no reads, no writes — and needs disk space for a full second copy. On a large production table this is an outage. Reserve it for cases where you must reclaim disk and can afford a maintenance window.

REINDEX CONCURRENTLY: online index rebuild

Index bloat is reclaimed by rebuilding the index. The online form builds a fresh copy alongside the old one under a weak lock and swaps them:

Lab-verified
SQL
REINDEX INDEX CONCURRENTLY idx_orders_customer;
REINDEX TABLE CONCURRENTLY orders;   -- every index on the table
Real captured output for this query is in the Pro lab notebook below.

This is the standard, safe remedy for index bloat. Like CIC, a failure can leave an INVALID index to drop, and it cannot run inside a transaction block.

pg_repack: online table compaction

To compact a table (not just its indexes) without the lock of VACUUM FULL, the pg_repack extension is the standard tool. It creates a shadow copy of the table, uses triggers to capture changes during the copy, builds the indexes, and swaps the new table in — holding the strong lock only for the brief final swap. The result is a fully compacted table with seconds of locking instead of minutes or hours:

Reference
SQL
-- Compact a bloated table online
pg_repack -d app --table orders
-- Reads and writes continue throughout; only a momentary lock at swap.

Layer 3 — Watch it happen on your own database

Do not rebuild blindly — measure. The pgstattuple extension reports actual dead space:

Lab-verified
SQL
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstattuple('orders');          -- table bloat: dead_tuple_percent
SELECT * FROM pgstatindex('idx_orders_pk');   -- index bloat: leaf_fragmentation

-- Cross-check the file size before/after a rebuild to prove space returned:
SELECT pg_size_pretty(pg_total_relation_size('orders'));
Real captured output for this query is in the Pro lab notebook below.

The number that decides everything is dead_tuple_percent (tables) or leaf_fragmentation (indexes). A high value justifies a rebuild; a low one means autovacuum is already keeping up and a VACUUM FULL would be a pointless outage. Re-check pg_total_relation_size after a pg_repack/REINDEX CONCURRENTLY to confirm space actually returned to the OS.

Layer 4 — The levers this hands you

There is an online tool for every cleanup goal — match the tool to the job:

  • Prevention beats cure. Tune autovacuum per-table on high-churn tables so bloat never accumulates in the first place.
  • Eliminate long transactions that hold back the vacuum horizon and let dead tuples pile up.
  • **Use appropriate fillfactor** to enable HOT updates and cut index bloat at the source.
  • **Avoid VACUUM FULL in production** — its ACCESS EXCLUSIVE lock and need for a full second copy make it an outage; reach for pg_repack instead.

Layer 5 — What an Oracle DBA should expect vs what they get

Bloat is the cost of PostgreSQL's MVCC model, and the tooling differs sharply from Oracle:

  • Bloat itself is more visible. Oracle's in-place updates + UNDO mean tables don't accumulate dead row versions the way Postgres heaps do — managing bloat is a uniquely Postgres discipline with no exact Oracle counterpart.
  • **VACUUM FULLALTER TABLE ... MOVE / online redefinition,** but the plain form is fully blocking. The online equivalent of Oracle's segment shrink (ALTER TABLE ... SHRINK SPACE) is pg_repack — an external extension, not built in.
  • **REINDEX CONCURRENTLYALTER INDEX ... REBUILD ONLINE,** with the same INVALID-on-failure caveat as CIC.
  • **Routine VACUUM has no real Oracle analogue** — there is no background process Oracle DBAs tune to reclaim dead-tuple space, because UNDO handles old versions differently. Autovacuum tuning is the new core skill.

Key takeaway

Routine VACUUM/autovacuum reclaims dead space online for reuse but does not shrink files; to return space to the OS use REINDEX CONCURRENTLY for indexes and pg_repack for tables — both online — and reserve the blocking VACUUM FULL for a maintenance window. Always measure first with pgstattuple, and prevent bloat at the source by tuning autovacuum, killing long transactions, and using fillfactor for HOT updates.

Interview lens

How this shows up in interviews

What an interviewer is actually testing, the same mechanism seen from three roles, and the gap between a shallow answer and one that shows real depth.

What the interviewer is testing

Whether you distinguish reclaim-in-place (plain VACUUM, cheap) from shrink-and-return-to-OS (VACUUM FULL/rewrite, blocking) and choose correctly.

Questions you should be able to answer

  • Does plain VACUUM return disk space to the OS? If not, what does?
  • VACUUM vs VACUUM FULL vs pg_repack — the tradeoffs?
  • How do you rebuild a bloated index without downtime?

Database engineer

You use plain VACUUM for reuse, REINDEX CONCURRENTLY for index bloat, and pg_repack (not VACUUM FULL) when you genuinely must shrink online.

Software engineer

You never casually run VACUUM FULL on a live hot table — it takes ACCESS EXCLUSIVE and rewrites the whole relation.

Feature builder

You prefer designs that avoid mass churn so a rewrite is rarely needed at all.

Shallow answer

'VACUUM FULL reclaims space, so run it to shrink.' Ignores that it's a blocking full rewrite.

Answer that shows depth

Plain VACUUM marks dead-tuple space reusable within the object but usually neither shrinks the file nor returns space to the OS. Returning space requires a rewrite: VACUUM FULL and CLUSTER take ACCESS EXCLUSIVE and rebuild the relation — an outage on hot tables — while pg_repack performs an online rebuild using triggers plus a final swap. Index bloat is addressed with REINDEX CONCURRENTLY. Choosing a blocking rewrite when in-place reuse would have sufficed is a classic self-inflicted outage.

Pro lab notebook

The captured run behind every query

Every SQL query above is real and the source citations are verified. Pro unlocks the captured psql output — each query re-run against a real PostgreSQL 17.10 lab, with its verbatim result.

Pro lab notebookTested, not just explained

1 real lab transcript for this lesson

The explanation and the SQL above are free — so are the verified source citations. What Pro unlocks is the proof: every query in Online VACUUM, REINDEX, and Table Rewrites re-run against a real PostgreSQL 17.10 instance, with its verbatim psql output captured line for line.

What Pro unlocks here

  • Every query in this lesson re-run against a real PostgreSQL 17.10 lab
  • The verbatim psql output — page headers, row versions, plan nodes, catalog rows
  • The corrected form of any query the source lesson got wrong, re-captured
  • A copy-ready notebook you can replay yourself to confirm every claim
Unlock with Pro — $24.99/moor $199/yr — save ~$100

How this was verified

This explanation is anchored to PostgreSQL's source tree — the files linked above, on the REL_17_STABLE branch — and cross-checked against the official documentation for versions 15 through 18. Every SQL query shown was executed and its output captured in a throwaway PostgreSQL 17.10 lab; where a query needed correcting to run, that correction is noted inline.

Official documentation: §25.1 Routine Reindexing