Webhook icon
If icon
Build icon
Commands icon
Docker icon
Queries icon
Parallel icon
Sequential icon
Query icon
LoopUntil icon
Fail icon
SlackIncomingWebhook icon
Pause icon

Online MySQL Schema Change with gh-ost and an Approved Cut-Over

Run gh-ost from Kestra with a dry run, replica-lag throttling, a row-count and checksum check, and a postponed cut-over that waits for approval.

Categories
DataInfrastructure

ALTER TABLE on a large, busy MySQL table locks or stalls writes and floods the replicas. gh-ost avoids that: it builds a "ghost" copy with the new schema, keeps it in sync from the binlog, then swaps the two tables in a short atomic cut-over. Most teams want a person to choose the moment of the swap, and proof that the copy is complete before anything is renamed.

This blueprint adds that around gh-ost.

How it works

  1. dry_run runs gh-ost without --execute. It connects, checks binlog settings, and applies the ALTER to an empty ghost table. A typo, a missing column or a rename without approval stops here, before any copy starts.
  2. migrate is a Parallel task with two branches that talk through a one-row control table (_<table>_kestra):
    • run_ghost starts gh-ost with --postpone-cut-over-flag-file, --max-lag-millis and --throttle-control-replicas, and every 5 s writes gh-ost's status line to the control row (copying, throttled or postponed);
    • gate waits until the copy is done and the cut-over is postponed. It then compares the original and ghost tables in one consistent read: row count and a BIT_XOR(CRC32(...)) checksum over the columns both tables have. Only then does it ask for approval (Pause) and write the decision.
  3. On approval, run_ghost removes the flag file and gh-ost swaps the tables. The previous table is kept as _<table>_del for you to drop later. On a decline or a timeout it sends gh-ost panic, and orders is never touched.
  4. finally drops _gho/_ghc, the empty sentry table a failed cut-over can leave, and the control table. It never drops the original or a _del table that holds data.

Error handling

  • Replica lag throttles the copy. In a test, stopping the replica's SQL thread for 20 s paused the copy, and the approval message reported "Throttled for about 20 s".
  • The cut-over never blocks writers for long. Each attempt holds its lock for at most cut_over_lock_timeout_seconds (3 s) and is retried cut_over_retries times. With a transaction holding the table for 20 s, the cut-over retried and then succeeded. When the lock was held through every retry, gh-ost gave up, orders was unchanged, and the alert quoted MySQL ("Lock wait timeout exceeded").
  • The swap needs proof first. If the ghost table does not match the original after 10 tries, no approval is requested, gh-ost is told to abort, and the ghost tables are removed.
  • Concurrent writes are expected. gh-ost applies binlog events with a small delay, so the comparison retries until the two tables agree. With 1500 inserts, updates and deletes during the copy and the wait, the tables matched and no row differed after the swap.
  • Every alert names the failing task and gh-ost's or MySQL's own error.
  • One run at a time. concurrency (limit 1, QUEUE) makes a new run wait while another one waits for approval; answer or kill the waiting run first.

What this blueprint teaches about Kestra

  • Parallel with two cooperating branches. A long-running worker task and a human gate (LoopUntil, Pause, JDBC writes) talk through a database row, without exposing gh-ost's socket.
  • Sequential with its own errors block, so a failed gate tells the other branch to stop instead of leaving gh-ost waiting forever.
  • Flow-level finally for cleanup that must run after success, a decline and a failure alike.
  • io.kestra.plugin.docker.Build, to build the tool image (gh-ost from source, mysql client) inside the flow.

Prerequisites

  • MySQL 5.7 or 8.x with binlog_format=ROW and binlog_row_image=FULL. A user with ALTER, CREATE, DELETE, DROP, INDEX, INSERT, LOCK TABLES, SELECT, TRIGGER, UPDATE, plus REPLICATION SLAVE, REPLICATION CLIENT and SUPER (or the cloud equivalent). See the gh-ost requirements.
  • A Kestra worker that can run Docker containers and reach MySQL.
  • A Slack incoming webhook.

Secrets

  • MYSQL_USERNAME, MYSQL_PASSWORD: the migration user, used by gh-ost and the JDBC tasks.
  • SLACK_WEBHOOK_URL.
  • GHOST_MIGRATION_WEBHOOK_KEY: a long random string for the pipeline webhook.

Inputs

  • host, port, database, table, and alter (the clause after ALTER TABLE <table>).
  • throttle_replicas (host:port,...), max_lag_millis (1500), chunk_size (1000), max_load (Threads_running=25), critical_load (Threads_running=500).
  • cut_over_lock_timeout_seconds (3), cut_over_retries (60), approval_timeout (PT24H).
  • ghost_image, and build_image (true builds it with the Docker Build task; set false when the image is in your registry).

Outputs

  • outputs.run_ghost.vars.throttled_seconds and cut_over_lock_timeouts; outputs.compare.row (row counts and checksums); outputs.final_state.row.state (done or aborted).

Quick start

  1. Add the secrets, set host, database, table, alter and throttle_replicas, and run.
  2. When Slack says "ready to cut over", check the numbers and resume with Swap the tables now? checked.
  3. Drop _<table>_del once you are sure you do not need to swap back.

How to extend

  • Run the cut-over in a maintenance window: put a Schedule in front, or call the webhook from your change pipeline.
  • Point host at a replica and remove --allow-on-master to have gh-ost inspect a replica, as gh-ost recommends.
  • Add --hooks-path scripts to gh-ost for your own notifications on throttling or cut-over.

Plugins used

See How

New to Kestra?

Use blueprints to kickstart your first workflows.