Business Impact of Anti-Wraparound Autovacuum

By John Doe July 29, 2026

Summary: In this article, we explore the impact of anti-wraparound Autovacuum on business SLA availability, based on a real-world case study.

Table of Contents

Event and Business Context

Incident Overview: On November 22, 2021, Duffel’s core flight search API experienced a complete outage lasting 2 hours and 17 minutes. This directly prevented downstream merchants from searching and booking flights, resulting in business losses.

Business Scenario: Flight search results are only valid for 30 minutes, generating massive amounts of expired data under high concurrency. The team optimized this using a PostgreSQL table partitioning solution: the table is partitioned by hour, with two cron jobs responsible for creating new partitions and dropping old ones respectively. Using DROP partition instead of batch DELETE significantly improved the efficiency of cleaning up expired data.

The Avalanche Effect: Complete Fault Propagation Path

Complete Fault Propagation Path

In the Duffel case, due to the massive volume of data in the flight search results table, the table age hit the 200 million threshold, triggering the anti-transaction ID wraparound autovacuum. The subsequent outage actually went through four very standard propagation steps:

  1. Emergency vacuum process starts and refuses to yield: Holds a SHARE UPDATE EXCLUSIVE lock. The anti-wraparound autovacuum initiates a full table scan, holding a SHARE UPDATE EXCLUSIVE lock, which normally does not interfere with standard read and write operations. However, because it operates in an “uninterruptible” mode, it becomes an immovable wall within the system.
  2. Scheduled DDL statements enter the wait queue: Requests a high-level lock like SHARE ROW EXCLUSIVE. At this exact moment, the business system triggers a DDL statement to automatically partition the table and drop old partitions (such as Duffel’s scheduled CREATE TABLE ... PARTITION OF). This operation requests a high-level table lock. Because the autovacuum stubbornly holds onto its lock, this DDL statement cannot execute and is suspended, entering the database’s Wait Queue.
  3. Core disaster: Business read/write requests are completely blocked: Requests a ROW EXCLUSIVE lock. This is the decisive moment that breaches the SLA. PostgreSQL’s lock queue follows a strict “first-come, first-served” principle. Even though normal business write operations (like INSERT) only require a weaker lock, because a DDL request with a very high exclusive level is already stuck at the front of the queue, all subsequent API business requests are deemed to conflict with that DDL and are forced to wait in line behind it.
  4. Connection pool exhausted, SLA drops to zero: Full-blown outage. Because read and write queries are blocked, the database’s wait queue rapidly lengthens. The application server’s (API) database connection pool is completely exhausted in a short period. New API requests cannot establish connections, directly causing widespread access errors across the business and severely destroying the availability SLA.

Core Principles: Why Does Autovacuum Block the Business?

Anti-Wraparound Autovacuum

The complete PostgreSQL mechanism behind this issue is as follows:

  1. Foundation: MVCC and Transaction ID Wraparound PostgreSQL implements Multi-Version Concurrency Control (MVCC) based on 32-bit transaction IDs (XIDs). The cyclical reuse of XIDs can lead to a “wraparound” problem—older data’s transaction IDs will appear larger than new transactions, rendering the data invisible or even resulting in logical data loss. One of the core responsibilities of VACUUM is to mark and “freeze” old rows, fundamentally preventing the wraparound risk.
  2. Lock Rules for Normal Autovacuum A normal autovacuum runs silently in the background, holding a SHARE UPDATE EXCLUSIVE lock. If the business side initiates a DDL statement that needs to modify the table structure, the normal vacuum process will “tactfully” interrupt itself and release the lock, having zero impact on business SLA.
  3. Crucial Exception: Anti-Wraparound Autovacuum is Uninterruptible When a table’s freeze age (the age of relfrozenxid) reaches the autovacuum_freeze_max_age threshold (default is 200 million), it triggers a mandatory anti-transaction ID wraparound VACUUM. The anti-wraparound autovacuum shoulders the life-or-death task of preventing data loss, so PostgreSQL internally sets it to be uninterruptible. This means that even if a full table scan takes several hours, it will fiercely hold onto its lock and will absolutely not yield to any conflicting operations.
  4. The Avalanche Effect of Lock Queuing Because the script for creating partitions did not have a lock timeout limit, it waited indefinitely for the lock, thereby blocking the entire wait queue. The team estimated that this problem was bound to erupt within 30 days at the latest. Ultimately, it manifested as the entire table becoming completely unreadable and unwritable, triggering a site-wide outage.

Best Practices for Safeguarding Business SLA

To prevent similar underlying protection mechanisms from becoming business killers, defense is needed on the following three levels:

  1. Add “Safety Valves” to DDL (Prevent Avalanches): This is the most painful lesson Duffel learned after falling into this trap. Whether running a database migration or a scheduled cron job, any statement involving DDL must have lock_timeout and statement_timeout configured. This way, when it cannot acquire a lock, it will “fail-fast” and exit the queue, rather than getting stuck there continuously blocking subsequent normal read/write traffic.
  2. Proactive Monitoring and Early Intervention (Prevent Triggers): Do not pin your hopes on the system’s emergency anti-wraparound mechanism. You should incorporate the age of pg_class.relfrozenxid into your core monitoring dashboards. Before it approaches the 200 million mark, manually execute VACUUM FREEZE during periods of low business traffic (such as early morning).
  3. Clarify Dependency Graphs (Rapid Recovery): When deadlocks or severe blockages occur, extract the process holding the lock and Wait queue logs via pg_stat_activity to quickly map out the dependency tree of lock waits. Directly use pg_terminate_backend() to kill the hanging DDL process (in Duffel’s case, the process executing the table creation statement), which will instantly unblock the business flow.

Of course, you can also consider using Redrock Postgres, which internally utilizes 64-bit transaction IDs and modifies table tuple records with rollback logs, eliminating the need for full data freezing.

References

Duffel Engineering: Understanding an outage: concurrency control & vacuuming in PostgreSQL

PostgreSQL Source Code: storage/lmgr/proc.c:ProcSleep()