IL-0392 Wikimedia Foundation · 2024-09-18database
MariaDB replication of section x1 between eqiad and codfw broke on a missing heartbeat row during a cookbook run
Before the next week's datacenter switchover, Wikimedia ran for the first time a cookbook that changes core databases from eqiad -> codfw replication to circular replication, so data flows back to eqiad when codfw becomes the only active datacenter. On statement-based sections pt-heartbeat writes with REPLACE, an upsert, and the step worked. On the row-based x1 and es sections the REPLACE became an update of an existing row, and because heartbeat rows had been cleaned up, that row existed on codfw servers but not on eqiad ones, so replication on eqiad x1 and es6 stopped with a missing-row error.
The first fix, deleting all heartbeat table rows, moved the x1 problem to codfw and started the user impact at 21:42:19 UTC. At 21:48:19 replication flowed again after heartbeat replication was skipped with SET GLOBAL Replication_wild_ignore_table='heartbeat.%'. The proper fix was to insert the missing heartbeat row on each affected host individually with binary logging off (set sql_log_bin=0): it was tested on es6, applied to es7 before that section was set up, and finally applied on the x1 codfw hosts in place of the ignore rule. The cookbook finished without further problems.
The later investigation traced the cause to a configuration change that, together with the cleanup of extra pt-heartbeat rows set up after Orchestrator, left the x1 and read-write es hosts using ROW format without making pt-heartbeat-wikimedia use STATEMENT for its own session. That was fixed in a later deploy.
- Severity
- –
- Scope
- For 6 minutes replication of the x1 section between eqiad and codfw stopped; x1-based services such as reading lists and echo could have served stale data, with no visible errors
- Users affected
- Clients in the Americas
- Region
- codfw
- Time to detect
- –
- Time to resolve
- 6m
Symptoms
On 18 September 2024, while a cookbook was reconfiguring Wikimedia's core MariaDB databases from one-way eqiad -> codfw replication to circular replication ahead of a datacenter switchover, replication in the codfw -> eqiad direction broke on the x1 and es6 sections. Engineers watching the run noticed at 21:28 UTC that the x1 primary was not replicating.
The replication error was "Could not execute Update_rows_v1 event on table heartbeat.heartbeat; Can't find record in 'heartbeat'". The statement-based sections s1 to s8 had completed without problems; x1 and the es hosts used row-based replication, and the row that pt-heartbeat updated did not exist on the eqiad servers.
Deleting the heartbeat table rows fixed eqiad but moved the break to codfw, in the eqiad -> codfw direction that carries real traffic. At 21:42:19 Icinga reported "PROBLEM - MariaDB Replica SQL: x1 on db2196 is CRITICAL: CRITICAL slave_sql_state Slave_SQL_Running: No, Errno: 1062, Errmsg: Could not execute Write_rows_v1 event on table heartbeat.heartbeat: Duplicate entry 180360966 for key PRIMARY". For 6 minutes x1 data could have been served stale to clients in the Americas by x1-based services such as reading lists and echo; no visible errors were produced.
The automation's check after setup was green, because replication broke only after pt-heartbeat was restarted on codfw, and monitoring of circular replication had not been enabled yet, so the first break in the codfw -> eqiad direction was found by people on orchestrator rather than by alerts.
Symptom Replication lag
Root cause
Root cause Configuration change
- Trigger
- Running the cookbook sre.switchdc.databases.prepare, which set up codfw -> eqiad replication and started pt-heartbeat, on the row-based x1 and es sections.
Detection
Engineers watching the first run of the automation saw on orchestrator that the x1 primary was not replicating, because monitoring of circular replication was not yet enabled. The break in the eqiad -> codfw direction was reported by Icinga.
Remediation
- Skipped replication of the heartbeat tables with Replication_wild_ignore_table='heartbeat.%'
- Inserted the missing heartbeat row with binary logging off on the es hosts
- Inserted the missing heartbeat row with binary logging off on the x1 codfw hosts and removed the ignore rule
Action items
- Avoid the same issue again, probably by inserting the missing row when it is detected
- Add a post-maintenance health check that replication works in both directions
- Add options to run the maintenance in another order or on each section individually
- Update the Phabricator task for each section rather than only at the end
- Fix the monitoring gaps for circular replication
- Made pt-heartbeat-wikimedia use STATEMENT format for its session on the x1 and row-based read-write es hosts, fixed in a later deploy
Lessons
Circular replication is not a normal working mode, so the first fix only moved the break to the other side; TRUNCATE would have worked better than DELETE because it always replicates as a statement, but it was not considered because it is more dangerous. Running sql_skip_slave_counter was avoided, since it could have made things worse or lost data. The check of the replication channel should also run once all tasks are complete, not only right after setup. The combination of row-based replication, pt-heartbeat and differing heartbeat rows was not described in earlier maintenance or documentation.
Sources
References
- https://wikitech.wikimedia.org/wiki/MariaDB/troubleshooting#Data_inconsistency_between_nodes_%22drift%22_/_replication_broken
- https://gerrit.wikimedia.org/r/c/operations/cookbooks/+/1074127
- https://gerrit.wikimedia.org/r/c/operations/software/spicerack/+/1074111
- https://gerrit.wikimedia.org/r/c/operations/cookbooks/+/1074128/1
Related cases
IL-0331An RDS MySQL Multi-AZ failover lost 5 seconds of committed data and broke replication on all five read replicasRazorpay · 2020same root cause · shared Database, Database replication
IL-0325DigitalOcean's control panel and API were down for 4 hours 56 minutes after the production primary database was deletedDigitalOcean · 2017same root cause · shared Database, Database replication
IL-0574Aurora MySQL writer CPU stuck near 100% minutes after Safie pointed production at a new Amazon RDS ProxySafie · 2026same root cause · shared Database
IL-0366Cloud Storage uploads and writes in us-central1 slowed and failed with HTTP 503 as traffic piled onto a few backendsGoogle Cloud · 2026same root cause · shared Database
IL-0179With DEBUG logging and synchronous log output, a high-traffic service filled the 64 KB stdout pipe, so request threads blocked in write() while holding database connections and HikariCP ran dry, failing requests in 30-second burstsMUSINSA · 2026same root cause · shared Database
IL-0561DigitalOcean's Cloud Control Panel and Public API returned up to 25% errors in two windows as authorization timed outDigitalOcean · 2026same root cause · shared Database
IL-0487Autumn's Postgres CPU spiked and about 10% of API requests failed after a collation change left the main query unindexedAutumn · 2026same root cause · shared Database
IL-0283Cloudflare's core proxy returned HTTP 5xx across its network after loading an oversized Bot Management feature fileCloudflare · 2025same root cause · shared Database
IL-0528Cloud Pub/Sub could not publish or subscribe in ten regions, and some ordered subscriptions stayed stuck for hoursGoogle Cloud · 2025same root cause · shared Database
IL-0374DigitalOcean creates and management actions failed globally when its service discovery datastore lost quorumDigitalOcean · 2024same root cause · shared Database replication
IL-0367Azure Storage in Central US rejected VM disk I/O, taking down VMs and the SQL Database and Cosmos DB services on themMicrosoft Azure · 2024same root cause · shared Database
IL-0511MIT xPro stopped responding after a configuration deploy as pgbouncer logins to RDS failed with 'wrong password type'MIT Open Learning · 2023same root cause · shared Database
IL-0272New Cassandra nodes served reads for data they did not hold, so Monzo records looked missing and payments failedMonzo · 2019same root cause · shared Database
IL-0430Mackerel's Redis stopped answering during a replication setup, keepalived failed over by mistake, and Mackerel degradedHatena · 2018same root cause · shared Database replication
IL-0292App Engine Memcache was down in all regions for 1 hour 50 minutes when its global datacenter mapping could not be readGoogle Cloud · 2017same root cause · shared Database
IL-0215Twilio's billing Redis master lost all balances and went read-only, and auto-recharge charged customer cards repeatedlyTwilio · 2013same root cause · shared Database replication
Check your cluster for this
For AI tools and scripts
- This case as JSON: https://incidentlib-api.benchlet.com/cases/IL-0392
- The cases most like it: https://incidentlib-api.benchlet.com/cases/IL-0392/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.