Skip to main content
Metrics type: Key MetricsCategory: Replication

At a glance

Replication Thread Health (IO/SQL) is the binary “is replication actually running?” check. A MySQL replica runs two threads: the IO (receiver) thread that pulls binlog events from the source into a local relay log, and the SQL (applier) thread that replays those events into the replica’s data. Both must report Yes. If either is No, replication is broken: the replica stops receiving or stops applying changes, and from that moment it drifts further from the source with every write. This is the card that catches replication failure the instant it happens, before lag has time to balloon.

Calculation

The card is a logical AND across both threads on every replica. There is no averaging and no percentile: replication is either running or it is not.
Two subtleties the engine handles:
  1. Connecting is not Yes. The IO thread can sit in a Connecting state when it is trying and failing to reach the source (bad credentials, network, or the source is down). The engine treats anything other than Yes as not healthy, so a flapping Connecting thread raises the alert rather than being read as a transient.
  2. The error fields carry the cause. When the SQL thread stops, Last_SQL_Error and Last_SQL_Errno explain why (a duplicate-key collision, a missing table, a statement the replica cannot apply). When the IO thread stops, Last_IO_Error explains the connection problem. The engine surfaces these so the alert is actionable, not just “broken”.

Worked example

A platform team runs a MySQL 8.0 source with two read replicas. Snapshot taken on 18 Apr 26 at 02:15 BST, shortly after a nightly maintenance job ran a manual DELETE directly on replica-a (a mistake: writes should only happen on the source). The headline reads Broken with a red border, and a Nerve Centre alert fires immediately. The team works it:
  1. The IO thread is fine, the SQL thread is stopped. replica-a is still receiving events into its relay log, but it has stopped applying them. Lag will now climb without bound until the SQL thread is restarted, because nothing is being applied.
  2. Last_SQL_Errno is 1062, a duplicate-key error. The manual DELETE removed a row, then the binlog from the source tried to apply a DELETE for a row that no longer existed, or a later INSERT collided. The replica’s data and the source’s binlog have diverged.
  3. The decision is correctness over speed. Skipping the offending transaction (SET GLOBAL sql_slave_skip_counter) would restart replication but leave the replica permanently inconsistent with the source. The team instead rebuilds replica-a from a fresh source snapshot so it is guaranteed consistent, and routes its catalogue traffic to replica-b in the meantime.
The rebuild completes by 03:40, both threads read Yes, and the card clears. The follow-up is to lock down replica write access so application credentials cannot run DML directly against a replica. Three takeaways:
  1. A stopped thread is a different emergency from high lag. Lag self-heals when the workload eases; a stopped thread never self-heals. It will sit broken until a human intervenes, and the divergence grows the whole time.
  2. The error number tells you whether you can recover or must rebuild. Transient errors (lost connection, source restart) usually resolve on a thread restart. Data-collision errors (1062 duplicate key, 1032 row not found) mean the replica has diverged, and the safe fix is a rebuild, not a skip.
  3. Never write to a replica. The single most common cause of a stopped SQL thread is a stray write directly on the replica. Enforce read_only / super_read_only and scope application credentials so only the source accepts DML.

Sibling cards

Reconciling against the source

Where to look in MySQL’s own tooling:
SHOW REPLICA STATUS\G on each replica is the canonical source. The fields that matter:
Performance Schema for the structured view: performance_schema.replication_connection_status (IO/receiver) and performance_schema.replication_applier_status_by_worker (SQL/applier, per worker). The replica’s error log for the full stack around the failure, often with more context than the single Last_SQL_Error line. Managed-service consoles: Amazon RDS surfaces replication state and will raise a Replication event; Aurora reports replica state in the cluster members view. These should agree with this card.
Why our number may legitimately differ: Cross-connector reconciliation: if you run an APM connector, a stopped applier often shows up downstream as stale-read errors or growing queue depth in the application. A broken thread here plus a spike in application error rate in Datadog confirms the failure is reaching customers, not just sitting in the database tier.

Known limitations / FAQs

The card says broken but the replica seems to be serving reads fine. How? A replica with a stopped SQL thread still answers queries; it just answers them from increasingly stale data. The IO thread may even still be running, filling the relay log. Everything looks alive until someone notices the data is old. That is precisely why this card exists: replication can be broken while the replica appears healthy. Which is worse, a stopped IO thread or a stopped SQL thread? Both are emergencies, but they fail differently. A stopped IO thread means no new events reach the replica at all (usually a connection, credential, or source-availability problem, often transient). A stopped SQL thread means events are arriving but cannot be applied (usually a data collision, which means the replica has diverged and may need a rebuild). The IO thread is more often recoverable with a restart; the SQL thread more often signals real divergence. Can I just skip the bad transaction to get replication running again? You can (sql_slave_skip_counter or injecting an empty GTID), and sometimes it is right for a known-benign event. But skipping a data-collision error leaves the replica permanently inconsistent with the source: every future read is potentially wrong, and the replica is unsafe to promote. For collision errors the safe path is a rebuild from a fresh source snapshot. Skip only when you understand exactly what you are dropping. Why did my SQL thread stop with a duplicate-key (1062) error? Almost always because something wrote directly to the replica. A row was inserted or deleted on the replica out of band, then the source’s binlog tried to apply a conflicting change. Enforce super_read_only on all replicas and scope application credentials so DML only ever hits the source. The thread shows Connecting and never reaches Yes. What now? The IO thread cannot establish a session with the source. Check, in order: network path and firewall to the source port, the replication user’s credentials and host grant, whether the source is up and accepting connections, and whether the source has purged a binlog the replica still needs (which requires re-seeding the replica). Last_IO_Error usually names the specific cause. Does this card cover Group Replication or InnoDB Cluster? This card reads the classic asynchronous / semi-synchronous replica threads. Group Replication exposes its member state through performance_schema.replication_group_members instead, which is a different model. If you run InnoDB Cluster, read member health from that table; the IO/SQL thread card applies to traditional source-replica topologies. Can I change what counts as healthy? The healthy condition (both threads Yes) is intrinsic to MySQL replication and is not configurable: there is no “partially running” that is safe. What you can configure in the Alert Rules tab is the paging behaviour and whether Connecting raises immediately or after a short grace period, useful if your network has known brief blips during source restarts.

Tracked live in Vortex IQ Nerve Centre

Replication Thread Health (IO/SQL) is one of hundreds of KPI pulses Vortex IQ tracks across MySQL and 70+ other ecommerce connectors. Nerve Centre runs the detection layer; Vortex Mind investigates the cause when something moves; Ask Viq lets you interrogate any number in plain English. Start for free or book a demo to see this metric running on your own data.