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

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:
- 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 EXCLUSIVElock, 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. - 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. - 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. - 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?

The complete PostgreSQL mechanism behind this issue is as follows:
- 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
VACUUMis to mark and “freeze” old rows, fundamentally preventing the wraparound risk. - Lock Rules for Normal Autovacuum
A normal autovacuum runs silently in the background, holding a
SHARE UPDATE EXCLUSIVElock. 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. - Crucial Exception: Anti-Wraparound Autovacuum is Uninterruptible
When a table’s freeze age (the age of
relfrozenxid) reaches theautovacuum_freeze_max_agethreshold (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. - 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:
- 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_timeoutandstatement_timeoutconfigured. 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. - 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.relfrozenxidinto your core monitoring dashboards. Before it approaches the 200 million mark, manually executeVACUUM FREEZEduring periods of low business traffic (such as early morning). - Clarify Dependency Graphs (Rapid Recovery): When deadlocks or severe blockages occur, extract the
process holding the lockandWait queuelogs viapg_stat_activityto quickly map out the dependency tree of lock waits. Directly usepg_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()