PostgreSQL Autovacuum & Table Bloat Calculator

Model dead tuple accumulation rates, autovacuum triggering schedules, table and index bloat, and cost-based vacuum I/O limits for high-write databases.

📁 Database🔒 100% Free & Private⚡ Instant Output
Select a common production database workload.
Current row count in the PostgreSQL table.
Bytes
Average byte length per tuple in pg_stats.
ops/sec
Every UPDATE and DELETE creates dead tuples in MVCC.
Default is 0.20 (20%). Recommended for large tables: 0.02 – 0.05.
Minimum dead tuples before triggering (default 50).
I/O credits per cycle (older default was 200; modern SSDs use 2,000+).
ms
Sleep duration when cost limit is hit (modern default: 2ms).

Autovacuum Cadence & Bloat Volume Summary

Vacuum Trigger Threshold 250,050 Dead tuples needed to start vacuum
Autovacuum Frequency Every 27.8 mins ~51 vacuum cycles per day
Peak Dead Tuple Bloat 62.5 MiB 5.0% of live table size
Max Vacuum Throughput 80.0 MB/s Fast NVMe configuration

Production Parameter Recommendations (Per-Table SQL & Global Conf)

-- PostgreSQL Tuning Commands
Quick Overview & Direct Answer

PostgreSQL Autovacuum & Table Bloat Calculator: Calculate PostgreSQL autovacuum trigger thresholds, dead tuple accumulation rates, table and index bloat, and cost limit I/O parameters to prevent database.

Primary Use SQL query optimization, schema formatting, relational data modeling
Input Formats SQL queries, table definitions, CSV/JSON tabular data
Output Format Beautified SQL, conversion dumps, diagram structures
Processing Mode 100% In-Browser Client-Side Processing
Access Tier Free, unlimited, no sign-up needed

The Inner Mechanics of PostgreSQL MVCC & Dead Tuple Bloat

PostgreSQL relies on Multi-Version Concurrency Control (MVCC) to deliver exceptional read-write concurrency without coarse table locks. However, MVCC introduces an architectural byproduct: whenever a row is updated (UPDATE) or removed (DELETE), PostgreSQL does not modify data in-place. Instead, it writes a completely new tuple version and writes the current transaction ID into the old tuple's xmax field.

Once all concurrent transactions that could observe the old tuple complete, that tuple becomes "dead." The space it occupies cannot be reclaimed by the operating system directly; it remains on disk inside 8 KB pages. The autovacuum background worker is responsible for scanning these pages, marking dead tuple slots as available in the table's Free Space Map (FSM), and preventing catastrophic table and index bloat.

The Mathematical Trigger Formula for Autovacuum

The PostgreSQL autovacuum daemon awakens periodically and checks whether each table has accumulated enough dead tuples to justify a vacuum pass. The mathematical threshold equation is:

$text{Vacuum Trigger Threshold} = text{autovacuum_vacuum_threshold} + (text{autovacuum_vacuum_scale_factor} times text{reltuples})$

Analyze Trigger Threshold:

$text{Analyze Trigger Threshold} = text{autovacuum_analyze_threshold} + (text{autovacuum_analyze_scale_factor} times text{reltuples})$

In default PostgreSQL configurations, autovacuum_vacuum_threshold = 50 and autovacuum_vacuum_scale_factor = 0.20 (20%).

The Scale Factor Trap: Why Large Tables Suffer Massive Bloat

While a 20% scale factor works adequately for small tables with 10,000 rows, it becomes a severe liability as tables scale into millions of rows:

For a table with 100,000 rows:

$text{Threshold} = 50 + (0.20 times 100,000) = mathbf{20,050text{ dead tuples}}$

For an enterprise table with 50,000,000 rows:

$text{Threshold} = 50 + (0.20 times 50,000,000) = mathbf{10,000,050text{ dead tuples!}}$

On the 50M-row table, autovacuum will not lift a finger until 10 million dead rows accumulate! If each row is 250 bytes, this permits over 2.5 Gigabytes of dead bloat to accumulate before a single vacuum run starts. This dead space inflates index trees, increases sequential scan times, and pollutes PostgreSQL's shared buffers with cold, dead pages.

Cost-Based Vacuum Throttling Mechanics

To avoid starving user queries of disk bandwidth, autovacuum operates on a token-based credit system. Every operation accrues a cost:

  • vacuum_cost_page_hit = 1 (Page found in shared buffers)
  • vacuum_cost_page_miss = 2 (Page read from OS kernel cache / disk)
  • vacuum_cost_page_dirty = 20 (Page modified and marked dirty for writing)

Once accumulated cost reaches autovacuum_vacuum_cost_limit, the worker sleeps for autovacuum_vacuum_cost_delay milliseconds:

$text{Max Vacuum I/O Rate (Pages/sec)} = frac{text{cost_limit}}{text{cost_delay (s)} times text{average cost per page}}$

On older PostgreSQL installs (pre-v12), the default limit was 200 with a 20ms delay, capping vacuum write speed to less than 4 MB/s. Tuning these values to cost_limit = 2000 and cost_delay = 2ms enables autovacuum to push 80 MB/s to 100 MB/s, easily keeping pace with heavy write transactions on modern NVMe drives.

Frequently Asked Questions

PostgreSQL uses Multi-Version Concurrency Control (MVCC) to provide non-blocking transactions. When a row is UPDATED, PostgreSQL does not overwrite the existing data in-place; instead, it writes a brand new version of the row (tuple) and marks the old tuple with a transaction expiration header (xmax). The old row becomes a 'dead tuple' that occupies disk space until cleaned by VACUUM.

By default, autovacuum triggers when dead tuples exceed: threshold + (scale_factor * reltuples), where default scale_factor is 0.20 (20%). For a 100,000-row table, autovacuum runs after 20,050 dead tuples accumulate. But on a 50,000,000-row table, autovacuum will not trigger until 10,000,050 dead tuples build up (generating tens of gigabytes of disk bloat). High-scale tables must have their scale_factor lowered to 0.01 or 0.02.

By default, PostgreSQL tables have a fillfactor of 100% (filling each 8 KB page completely). Setting fillfactor to 85% leaves 15% free space in each page. When non-indexed columns are updated, PostgreSQL can place the new row version in the same page and create a micro-pointer from the old tuple, known as a Heap-Only Tuple (HOT) update. This avoids updating table indexes entirely and allows instantaneous pruning without full vacuum.

PostgreSQL throttles vacuum background I/O to avoid saturating disks. In older PostgreSQL versions, the default autovacuum_vacuum_cost_limit was 200 with a 20ms delay, capping vacuum throughput to ~4 MB/s (sized for legacy rotational disks). On fast modern NVMe SSDs capable of 2,000+ MB/s, this default causes vacuum to fall hopelessly behind active writes. Setting cost_limit to 2,000–5,000 with a 2ms delay unlocks 50 to 100 MB/s of background vacuum throughput.