IL-0211 Mailchimp · 2019-02-04database
A Mandrill PostgreSQL shard shut down at transaction ID wraparound and email sending was impaired for about 40 hours
Every write in PostgreSQL gets a transaction ID (XID) from a 32-bit counter that is compared modulo 2^32, so old XIDs must be cleared by the autovacuum process before about 2 billion newer transactions make them wrap around and look like the future. Mandrill keeps its data in a set of PostgreSQL databases that act as one key-value store sharded by key. The hashing algorithm favoured shard4, which took a higher write load than the other shards. In November 2018 Mandrill engineers had seen XIDs climb to about half the limit at peak load, judged that wraparound was not an immediate threat and put extra monitoring on the backlog. On shard4 autovacuum likely fell behind under the higher load, or it may have failed; Mailchimp could not tell which. At 05:35 UTC on 4 February the XID reached the limit and the database went into safety shutdown mode.
The first exception was lost among unrelated exceptions, and engineers started investigating at 11:58 UTC, when retries of failed writes had already raised the load on shard4. The wraparound was found at 12:56 UTC and a VACUUM in standalone mode was started at 14:16 UTC. Its runtime was estimated in days or weeks, so a dump and restore without the large Search and Url tables was started alongside it, also too slowly. Failed jobs queued on the app servers' disks, so storage volumes were replaced across the app servers, and the job-queue tables were moved from shard4 to other shards, which helped little.
At 16:30 UTC on 5 February responders met to look at more radical options. Having established that the app could run without the Search and Url tables, and verified that dropping them would free their XIDs, the team truncated the tables on the locked database at 19:12 UTC. The vacuum finished about an hour later, sending from the backlog began at 21:36 UTC and the outage was announced as resolved at 22:09 UTC. Alerting for XID wraparound was in place by 22:40 UTC, and by 01:00 UTC on 6 February the queues were drained. Mailchimp refunded affected users for Mandrill purchases made between 1 January and 13 February 2019 and credited Mandrill blocks for February.
- Severity
- –
- Scope
- Mandrill email sending: only 80% of queued emails were sent from 05:35 UTC on 4 February until 22:09 UTC on 5 February 2019
- Users affected
- Mandrill users; affected users were refunded for purchases made between 1 January and 13 February 2019
- Region
- –
- Time to detect
- 6h 23m
- Time to resolve
- 40h 34m
Symptoms
From 05:35 UTC on 4 February 2019 Mandrill, Mailchimp's transactional email service, sent only 80% of queued emails, because one of its PostgreSQL shards, shard4, stopped accepting writes. When Mandrill engineers opened an investigation at 11:58 UTC, they saw a sharply elevated load on shard4.
The first exception had been logged overnight, but unrelated exceptions from other systems buried it in the centralized error reporting. The rising load on shard4 came from retries of the failed writes, and while responders looked for its source, they found at 12:56 UTC that shard4 had reached the upper limit of its transaction IDs (XID) and gone into safety shutdown mode.
The failure spread to the Mandrill app servers. Jobs that had to write rows to shard4 failed and were queued on the app servers' local disks, and those jobs and the logs of their failures filled the disks until every app server was low on space. Logs of malformed API requests were hard to tell apart from real application errors, job health was hard to see, and the low disk space made partial code deployments for triage risky.
The standard recovery was too slow. VACUUM in standalone mode, started at 14:16 UTC, was estimated to need many days even after it was restarted with settings that sped it up, with a highest estimate of 40 days. A dump and restore of the database without the Search and Url tables, which were terabytes in size while most tables were megabytes, was faster but still far too slow. Moving the job-queue tables kept on shard4 to other shards helped little, because those jobs still had to write other data to shard4. Mandrill deletes queued mail older than 48 hours by default, so time was running out for the backlog.
Symptom Writes rejectedThroughput degradationLoad imbalanceNode disk full
Root cause
Root cause Capacity exhaustion
- Trigger
- At 05:35 UTC on 4 February the transaction ID counter of shard4, the busiest Mandrill PostgreSQL shard, reached its upper limit and PostgreSQL shut the database down to protect against wraparound.
Detection
The first exception was logged overnight but was buried by unrelated exceptions in the centralized error reporting. Mandrill engineers opened an investigation at 11:58 UTC and, while looking for the source of the rising load on shard4, found the wraparound at 12:56 UTC.
Remediation
- Put shard4 into standalone mode and started VACUUM at 14:16 UTC on 4 February
- Stopped and restarted the vacuum with settings that made it run faster
- Started a dump and restore of the database without the Search and Url tables while the vacuum ran
- Replaced storage volumes on the Mandrill app servers whose disks were filling with queued jobs and logs
- Moved the job-queue tables from shard4 to other live shards
- Truncated the Search and Url tables on the locked database at 19:12 UTC on 5 February, after which the vacuum finished in about an hour
- Started sending from the backlog at 21:36 UTC
- Added alerting for XID wraparound by 22:40 UTC
Action items
–
Lessons
Mailchimp named the factors that slowed its response: knowledge of and access to Mandrill systems and monitoring rested with a few people, Mandrill logging needed a locally writable disk, logs of malformed API requests were hard to separate from real application errors, job health was hard to see, and the disk space problem made partial deployments for triage risky. What helped was trained incident commanders with clear roles (incident commanders in 3-hour rotations, scribes, technical liaisons and points of contact between teams), periodic summaries with a list of recent and open decisions, and taking time in a video meeting to reassess, which led to the truncation. Running in the cloud let the team expand storage quickly, and the team was lucky that the largest table holding up the vacuum held data it could delete.
Sources
Related cases
IL-0553CircleCI's orchestration database filled its transaction log volume, and no workflows or jobs started for 50 minutesCircleCI · 2026same root cause · shared PostgreSQL vacuum, Database
IL-0550Bitbucket Cloud's database slowed until connection pools saturated and requests timed out, leaving Pipelines pendingAtlassian · 2024same root cause · shared PostgreSQL vacuum, Database
IL-0456Inflearn went down six times during a January 2022 free-course event as slow queries piled up on Aurora PostgreSQLInflab · 2022same root cause · shared PostgreSQL vacuum, Database
IL-0450Oracle RAC commits behind order APIs kept waiting over 0.1 s on log file sync while average host CPU had headroomToss Securities · 2026same root cause · shared Database
IL-0379InfluxDB Cloud queries in Azure West Europe slowed and timed out as storage pods fell behind and kept restartingInfluxData · 2026same root cause · shared Database
IL-0309Preset's RDS PostgreSQL 14.7 to 17.9 upgrade in US East 1 ran out of storage and took Superset down for six hoursPreset · 2026same root cause · shared Database
IL-0435Error rates rose as Aurora MySQL killed connections and declined queries to avoid OOM while load metrics stayed flatCyberAgent · 2026same root cause · shared Database
IL-0497Buttondown's database ran out of connections twice in one day and requests failed while health checks mostly passedButtondown · 2026same root cause · shared Database
IL-0288ZooKeeperWait rose to thousands of seconds on three redundant ClickHouse instances, degrading a CDN77 data pipelineCDN77 · 2026same root cause · shared Database
IL-0350Hosts in an IONOS TXL cluster lost connectivity when an InfiniBand fabric resweep failed on a full multicast tableIONOS Cloud · 2026same root cause · shared Database
IL-0305Replicas of one Intercom Vitess shard hit 100% CPU and fell up to 2.2 hours behind, serving stale inbox dataIntercom · 2026same root cause · shared Database
IL-0390PostHog US Cloud event ingestion slowed to delays of up to 2 days as writes to the Persons Postgres table stalledPostHog · 2025same root cause · shared Database
IL-0100PostHog's feature flags pods were packed onto Kubernetes nodes that were too small for their CPU requests, and CPU saturation above 90% caused three outages in ten days through Redis overload, connection pool exhaustion and slow queriesPostHog · 2025same root cause · shared Database
IL-0287One overloaded shard of MongoDB's internal monitoring database delayed Atlas metrics, alerts and cluster operationsMongoDB · 2025same root cause · shared Database
IL-0557Database transaction locks piled up on Storj's US1 satellite, and uploads and downloads failed with timeouts for 2 hoursStorj · 2025same root cause · shared Database
IL-0551Bitbucket Cloud's overloaded primary database failed over, and the website, API and Pipelines were slow for almost a dayAtlassian · 2025same root cause · shared Database
IL-0298Wikimedia edits failed for 38 minutes when sessionstore Cassandra nodes in both data centres crashed on full disksWikimedia Foundation · 2025same root cause · shared Database
IL-0312Gadget's main Postgres did not start again after a restart to raise max_connections, and the platform went downGadget · 2024same root cause · shared Database
IL-0544Wasabi's database cache stopped responding when its queueing service filled up, and S3 failed in all regionsWasabi · 2024same root cause · shared Database
IL-0447An every RDS for MySQL instance slowed badly when its gp2 EBS BurstBalance ran out and IOPS fell to the baselineevery · 2024same root cause · shared Database
IL-0375MongoDB Atlas cluster changes and alerts lagged for hours after a control plane metadata migration congested a databaseMongoDB · 2024same root cause · shared Database
IL-0110A Grafana Agent pod's high memory use and crash-looping Hangfire pods drove a Kubernetes node into system OOM and NotReady, twice in one dayDFDS · 2024same root cause · shared Database
IL-0270Discord's ScyllaDB authentication cluster overloaded during an OS upgrade, and logins failed for about 50 minutesDiscord · 2023same root cause · shared Database
IL-0202A burst of queries and database writes exhausted connection pools and memory on Honeycomb's ingest hosts, and Kubernetes crash-loop back-off kept failed hosts offline so ingest capacity stayed too low to recoverHoneycomb · 2023 · sev1same root cause · shared Database
IL-0386After a move to Google Cloud, Postgres connection timeouts and memcached i/o timeouts spiked on GKE nodesincident.io · 2023same root cause · shared Database
IL-0326A retention clean-up job filled the disk of MailPace's managed Postgres, which stayed offline for just over two hoursMailPace · 2023same root cause · shared Database
IL-0546Buildkite jobs could not go to newly connected agents once agent IDs passed a 32-bit integer foreign key's limitBuildkite · 2023same root cause · shared Database
IL-0500UToronto JupyterHub logins and server starts returned 500 errors when the hub's sqlite database disk filled up2i2c · 2022same root cause · shared Database
IL-0301Basecamp 3's primary database went into total gridlock at the top of the hour and Basecamp 3 went down twice37signals · 2022same root cause · shared Database
IL-0206Consul agents in GitLab.com's GKE pods were repeatedly OOM-killed, so the Rails pods lost database replica discovery and sent every query to the primaryGitLab · 2022same root cause · shared Database
IL-0302Peak load on GitHub's mysql1 MySQL cluster maxed out its database proxy's connections and stopped writes in March 2022GitHub · 2022same root cause · shared Database
IL-0212Many Slack users could not connect because a Vitess keyspace for channel membership was overloaded by cache-miss queriesSlack · 2022same root cause · shared Database
IL-0321GCP snapshots on all Patroni nodes stalled GitLab.com's primary PostgreSQL disk, Patroni failed over and 5xx spikedGitLab · 2021same root cause · shared Database
IL-0496Ably's Cassandra hit 100% CPU in every region and queries timed out globally, and the load returned on two more eveningsAbly · 2020same root cause · shared Database
IL-0443A Repro Aurora MySQL reader restarted unexpectedly and writer insert latency rose about sevenfold for a day or moreRepro · 2019same root cause · shared Database
IL-0037Image registry rate-limited a whole region after a crash-looping DaemonSet with imagePullPolicy Always pulled through three NAT instancesDatadog · 2019same root cause · shared Database
IL-0297Mezmo users could not sign in and log indexing lagged after the auth cache's Redis servers were OOM killedMezmo · 2019same root cause · shared Database
IL-0064CPU-heavy pods packed onto one Google Cloud node drove it to kernel panics, and the crash moved from node to node until the site was downMoonlight · 2019same root cause · shared Database
IL-0300Basecamp 3 went read-only for almost five hours when its events table reached the ceiling of 2,147,483,64737signals · 2018same root cause · shared Database
IL-0334Instapaper's RDS MySQL bookmarks table hit the 2TB ext3 file size limit, inserts failed and the service went downInstapaper · 2017same root cause · shared Database
IL-0333A downsized RDS PostgreSQL database maxed out its CPU at US peak and failing ELB health checks took buildkite.com downBuildkite · 2016same root cause · shared Database
IL-0282DynamoDB in US-East reached about 55% request errors as storage servers overloaded its metadata serviceAmazon Web Services · 2015same root cause · shared Database
IL-0383PagerDuty's shared Cassandra cluster degraded during a repair, delaying notifications and losing incoming eventsPagerDuty · 2014 · sev1same root cause · shared Database
IL-0320Postmark's primary MySQL disk filled but MySQL kept running, so MySQL-MMM never failed over and sending was delayedPostmark · 2011same root cause · shared Database
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0211
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0211/similar
- MCP server (Streamable HTTP, no key):
https://incidentlib-mcp.benchlet.com/mcp
The API needs no key. The MCP server offers search_incidents, get_incident, list_incidents, get_topic and vocab; both are explained on API and MCP.