Schedule icon
Query icon
OutputValues icon
Log icon
Switch icon
SlackIncomingWebhook icon
Fail icon
If icon
Pause icon
Sleep icon

MySQL Replica super_read_only Enforcement Guard

Detect a MySQL replica with super_read_only OFF, enforce it behind a dry-run and approval gate, then re-read and fail if the replica is still writable.

Categories
DataInfrastructure

A read replica with super_read_only OFF accepts a stray write from any client that reaches it. That write exists only on the replica, so it becomes an errant transaction: the replica's GTID set diverges from the source. The moment that replica is promoted the data is wrong in a way no backup catches. The failover plan quietly stops being safe. This guard finds a replica that is still writable, sets super_read_only ON behind an approval gate, then proves the protection landed.

This blueprint was created by zkasuran.

Safety

  • The whole remediation is gated on SHOW REPLICA STATUS returning a row. An instance with no replication configured is reported and left exactly as it was, so a source pointed at by mistake is never frozen. That refusal is the most important behaviour in this flow, because enforcing read-only on a source stops every application write.
  • Read-only by default. dry_run is true and auto_remediate is false, so a plain run reports and changes nothing.
  • A change happens only when auto_remediate is true and dry_run is false. It then waits for a human to resume the Pause with approved set true. An unanswered gate reads as not approved.
  • SET GLOBAL super_read_only = ON does not affect the replication applier threads, so the replica keeps applying from the source. The run re-reads SHOW REPLICA STATUS to show that.
  • The change is not persisted to mysqld-auto.cnf and survives only until restart. Put super_read_only=ON in the replica config as well so a restart comes back protected.

How it works

  1. detect_replication (io.kestra.plugin.jdbc.mysql.Query) reads SHOW REPLICA STATUS. A returned row is the only proof that the instance is a replica.
  2. detect_state (io.kestra.plugin.jdbc.mysql.Query) reads @@global.super_read_only, @@global.read_only, the server id, the hostname and the executed GTID set.
  3. classify (io.kestra.plugin.core.output.OutputValues) sorts the instance into one state. NOT_A_REPLICA is zero status rows. COMPLIANT is a replica with super_read_only already ON. WRITABLE_REPLICA is a replica with super_read_only OFF.
  4. log_status (io.kestra.plugin.core.log.Log) records both variables, the replication threads and the GTID set.
  5. route (io.kestra.plugin.core.flow.Switch) branches on the classification. COMPLIANT logs and ends. NOT_A_REPLICA alerts, fails and changes nothing.
  6. For WRITABLE_REPLICA, writable_alert posts the exposure. mode (io.kestra.plugin.core.flow.If) enters remediation only when auto_remediate is true and dry_run is false. Otherwise report_only logs what would run.
  7. approval_gate (io.kestra.plugin.core.flow.Pause) holds the run until a human resumes it with approved. enforce_super_read_only then runs SET GLOBAL super_read_only = ON, which also sets read_only ON.
  8. reverify_state re-reads both variables and reverify_replication re-reads SHOW REPLICA STATUS. verify_result fails the run through io.kestra.plugin.core.execution.Fail unless both variables come back ON.

What you get

  • A check that tells a replica apart from a source before it changes anything, so the guard is safe to schedule across a fleet.
  • An enforcement step that only ever runs after a dry run, an explicit mode switch and a human approval.
  • Real proof. The run passes only when a second read shows super_read_only and read_only both ON, with the replication threads still running.
  • Slack alerts on a writable replica, on a target that is not a replica and on any failure.

Who it's for

  • Platform engineers who keep MySQL read replicas behind a failover plan and need the write protection to be true rather than assumed.
  • DBAs who have had to clean an errant transaction out of a replica's GTID set after a stray write.
  • Teams running a fleet of replicas where one rebuilt or restarted node comes back without the read-only settings.

Why orchestrate this with Kestra

MySQL has no scheduler, no approval step and no way to prove a variable change landed. Kestra adds all of it. It polls each instance, refuses to act on anything that is not a replica, gates the change behind dry run and human approval, re-reads the server to turn a hopeful SET GLOBAL into a verified one, shares one connection definition across every task and posts to Slack the moment a replica is found writable or a step fails.

Tested end to end

Run on Kestra 2.0.5 against a MySQL 8.0 source and replica pair with GTID replication.

  • Detector can fail: the same replica returned COMPLIANT with super_read_only ON, then WRITABLE_REPLICA after SET GLOBAL super_read_only = OFF.
  • Dry run on a writable replica detected it, alerted and changed nothing (@@super_read_only still 0 in the mysql client).
  • Remediate with approval moved the replica from super_read_only 0 and read_only 0 to 1 and 1, confirmed in the mysql client, with both replication threads still Yes.
  • The approval gate held. A run resumed with approved false ran no statement and left the replica writable.
  • Pointed at the source, the flow classified NOT_A_REPLICA, refused to act and failed. The source stayed super_read_only 0 and read_only 0, verified in the mysql client after the run.
  • A connecting user without SYSTEM_VARIABLES_ADMIN had its SET GLOBAL rejected. The re-read reported OFF and the run failed instead of reporting success.
  • An unreachable instance failed the first read and fired the Slack alert from the errors block.

Prerequisites

A MySQL replica reachable over JDBC. The connecting user needs REPLICATION CLIENT to read SHOW REPLICA STATUS and SYSTEM_VARIABLES_ADMIN to set super_read_only. Without the second privilege the flow still detects and reports. The re-read then fails the run rather than claiming success.

Secrets

  • MYSQL_USERNAME: MySQL user, read when use_secret_auth is true.
  • MYSQL_PASSWORD: password for that user.
  • SLACK_WEBHOOK_URL: used by the alerts and the errors block.
  • In Kestra OSS, provide each secret as a base64-encoded environment variable prefixed SECRET_.

Inputs

Name Type Default Description
target_url STRING jdbc:mysql://localhost:3306/ JDBC URL of the instance to check.
use_secret_auth BOOL true Read credentials from secrets. Set false only for inline test credentials.
mysql_username STRING root Username used only when use_secret_auth is false.
mysql_password STRING `` Password used only when use_secret_auth is false.
dry_run BOOL true Report without changing the variables.
auto_remediate BOOL false Enforce behind the approval gate when dry_run is false.

Quick start

  1. Set MYSQL_USERNAME, MYSQL_PASSWORD and SLACK_WEBHOOK_URL as namespace secrets.
  2. Import this flow.
  3. Run it with target_url pointed at a replica. The default dry run reports both variables and the replication state.
  4. To enforce, run again with dry_run false and auto_remediate true, then resume the paused execution with approved true.

How to extend

  • Check a fleet from one flow by moving the two reads and the enforcement into a loop over replica URLs with concurrencyLimit: 1.
  • Compare @@global.gtid_executed against the source's gtid_executed with GTID_SUBTRACT to report an errant transaction that was already written.
  • Add read_only_admin_check style gates for innodb_read_only or for a proxy that still routes writes to the replica.
  • Fail the run as soon as a writable replica is found, for a compliance check with no remediation at all.
  • Write the finding to a ticket so the config fix that makes the setting survive a restart is tracked.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.