DBRaven
Write AmplificationCritical

Storage Bloat Without Archiving or Retention Policy

A write-heavy table: audit logs, events, time-series metrics: grows unbounded without a retention or archiving policy. Dead tuples from high-churn UPDATE and DELETE operations accumulate faster than autovacuum can reclaim them. Table bloat causes sequential scans to read tens of millions of dead pages, the query planner degrades on stale statistics, and autovacuum becomes the dominant I/O consumer on the host.

PostgreSQL primary with a high-churn table growing 5-15GB/month and no partitioning or archiving

Degradation Replay

Stage 1

Nominal: Table Growing but Vacuum Keeping Pace

Nominal
Trigger

Table under 10GB; autovacuum triggering regularly and reclaiming dead tuples efficiently

Operational Metrics
Dead Tuple Ratio
4 %
warn 10crit 30
Table Size
8.5 GB
warn 50crit 200
Hours Since Last Autovacuum
6 hours
warn 24crit 72
Symptoms
  • ·n_dead_tup < 5% of n_live_tup for all tables
  • ·Table size growth rate matches expected business data volume
  • ·pg_stat_user_tables.last_autovacuum within last 24 hours for high-churn tables
  • ·Sequential scan times under 50ms for typical business queries
Topology Effects
  • ·Autovacuum running on small tables completes in minutes: low I/O overhead
  • ·Table bloat ratio (actual_size / live_data_size) below 1.3×
Operational Consequences
  • !Normal operation: autovacuum keeping pace with churn rate

Operational simulation model only, not a production forecast. Degradation stages are derived from structured operational knowledge, not measured telemetry. Do not use for capacity planning or incident response.

Run With Your Parameters

Adjust the parameters below to see how metric values shift across degradation stages. Formulas are deterministic: same inputs always produce the same output.

Simulation Parameters

Computed Degradation Stages

nominal·Nominal: Table Growing but Vacuum Keeping Pace

Table under 10GB; autovacuum triggering regularly and reclaiming dead tuples efficiently

Write RPS
800req/s
warn: 1,600crit: 2,000
Write Amplification
4.2×
warn: 3crit: 6
WAL Throughput
0.82MB/s
warn: 100crit: 160
Disk Write Utilization
0.41%
warn: 60crit: 85
P99 Write Latency
5ms
warn: 20crit: 200
Storage Accumulation Rate
0.2MB/s
warn: 5crit: 50
degraded·Degraded: Bloat Accumulating Between Vacuum Runs

Table reaches 20-50GB; autovacuum duration extends; dead tuple ratio rising between runs

Write RPS
1,300req/s
warn: 1,600crit: 2,000
Write Amplification
4.2×
warn: 3crit: 6
WAL Throughput
1.33MB/s
warn: 100crit: 160
Disk Write Utilization
0.67%
warn: 60crit: 85
P99 Write Latency
5ms
warn: 20crit: 200
Storage Accumulation Rate
0.32MB/s
warn: 5crit: 50
warning·Warning: Autovacuum Cannot Keep Pace

Table exceeds 50GB; autovacuum runs take 3-4 hours; dead tuple accumulation rate exceeds vacuum rate

Write RPS
1,700req/s
warn: 1,600crit: 2,000
Write Amplification
4.2×
warn: 3crit: 6
WAL Throughput
1.74MB/s
warn: 100crit: 160
Disk Write Utilization
0.87%
warn: 60crit: 85
P99 Write Latency
5ms
warn: 20crit: 200
Storage Accumulation Rate
0.42MB/s
warn: 5crit: 50
critical·Critical: Disk Exhaustion and Query Planner Breakdown

Table bloat consumes available disk; query planner degradation causes application query regressions

Write RPS
2,200req/s
warn: 1,600crit: 2,000
Write Amplification
4.2×
warn: 3crit: 6
WAL Throughput
2.26MB/s
warn: 100crit: 160
Disk Write Utilization
1.13%
warn: 60crit: 85
P99 Write Latency
5ms
warn: 20crit: 200
Storage Accumulation Rate
0.54MB/s
warn: 5crit: 50
recovery·Recovery: Archiving and Retention Policy Implemented

Retention DELETE job runs; archiving pipeline removes old data to object storage; table size stabilizing

Write RPS
1,000req/s
warn: 1,600crit: 2,000
Write Amplification
4.2×
warn: 3crit: 6
WAL Throughput
1.03MB/s
warn: 100crit: 160
Disk Write Utilization
0.51%
warn: 60crit: 85
P99 Write Latency
5ms
warn: 20crit: 200
Storage Accumulation Rate
0.24MB/s
warn: 5crit: 50

Interpretation

healthy

4 secondary indexes create 4.2× write amplification. Peak disk utilization: 1% of 200 MB/s bandwidth.

Bottleneck

Secondary index write amplification exhausting disk write bandwidth

Recommendation

Reduce index count from 4 to essential indexes only. Use partial indexes on hot write paths. Upgrade disk to 400 MB/s write bandwidth for headroom.

Parameterized simulation: not a production forecast. Values derived from deterministic formulas applied to your parameters. Do not use for capacity planning or operational decisions without validation.

Propagation Model

Linear
Unbounded Table GrowthDisk Storage, Sequential Scan Cost

Table size grows monotonically; sequential scans must read all allocated pages including those holding only dead tuples; scan time grows O(N) with table size

Stabilizes: No stabilization without manual intervention: grows indefinitely

Threshold
Autovacuum Threshold (scale factor)Dead Tuple Accumulation, Table Bloat

Default autovacuum_vacuum_scale_factor=0.2 means autovacuum triggers only when 20% of 50M rows (10M rows) are dead: at 500 updates/second this takes 20,000 seconds (5.5 hours) before vacuum even starts

Stabilizes: Lowering scale factor to 0.02 triggers autovacuum 10× earlier: requires explicit tuning

Feedback Loopmild amplification
Autovacuum I/O LoadOLTP Query Latency, Autovacuum Interruption

Large autovacuum job consumes disk I/O bandwidth; OLTP query latency rises; if autovacuum exceeds cost_delay budget or is manually cancelled by ops, vacuum aborts mid-run and dead tuples re-accumulate

Stabilizes: Stabilizes when autovacuum completes a full pass: but on a 100GB bloated table this can take 4-8 hours

Recovery Patterns

Bulk delete with retention window + VACUUM ANALYZE

Deletion: 1-4 hours depending on data volume; vacuum: 2-6 hours after deletion
Tradeoffs
  • ·Large DELETE generates significant WAL and may cause replication lag on standbys
  • ·Bulk deletion creates additional dead tuples: temporary bloat spike before vacuum reclaims them
Residual Risks
  • !VACUUM reclaims pages for PostgreSQL reuse but does not shrink table file on disk: need VACUUM FULL or pg_repack to return disk space to OS

Migrate to partitioned table with time-range partitions

1-2 days for table reconstruction; ongoing management via pg_partman
Tradeoffs
  • ·Partition DDL migration requires application changes and potentially downtime
  • ·Partition pruning must be verified for all query patterns
Residual Risks
  • !Queries that previously used btree index on timestamp may need query plan validation after partitioning

Operational Summary

Storage bloat without archiving is a slow-moving incident that arrives months after the engineering decision that caused it. The default PostgreSQL autovacuum settings are calibrated for small-to-medium tables with moderate churn. A high-write table growing at 10GB/month will outpace default autovacuum within a year.

The first symptom is often noticed by a reporting query that suddenly starts taking 60 seconds instead of 200ms. The query plan changed because statistics are stale and the planner no longer knows the table's shape. The root cause is 60% dead tuples that have never been reclaimed.

The structural fix is partitioning with time-range partition drops: instead of vacuuming dead tuples out of a monolithic table, you drop entire old partitions in milliseconds. This is the correct architecture for any table where data has a natural time-based retention policy.

Storage Bloat Without Archiving or Retention Policy: DBRaven