Schedule icon
Query icon
OutputValues icon
Log icon
If icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Fail icon

ClickHouse Stuck Mutation Killer Guard

Detect failing ClickHouse mutations in system.mutations, kill them behind a dry-run and approval gate, then re-read to confirm none are still pending.

Categories
DataInfrastructure

An async ClickHouse mutation (ALTER TABLE ... UPDATE or DELETE) can wedge. A bad expression makes it throw on every retry, so it sits in system.mutations with is_done = 0 and a latest_fail_reason, blocking later mutations on the same table and holding merge resources. This guard finds those mutations and kills the failing ones under a gate, then proves none are still pending.

This blueprint was created by zkasuran.

Safety

  • Read-only by default. dry_run is true and auto_remediate is false, so a plain run reports and never kills.
  • A kill 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.
  • Failing mutations are the default target. A mutation that is merely slow but still progressing is left alone unless you opt in with include_stuck_without_failure, because killing it would discard the work it has already done.
  • Each kill is scoped by database, table and mutation_id and runs one statement at a time. The re-read of system.mutations is the real gate, so a kill the user is not allowed to run surfaces as a failed run rather than a false success.

How it works

  1. detect (io.kestra.plugin.jdbc.clickhouse.Query) reads system.mutations for the database and keeps rows with is_done = 0 that either have a non empty latest_fail_reason or, when include_stuck_without_failure is true, were created more than stuck_minutes ago.
  2. summarize (io.kestra.plugin.core.output.OutputValues) records the targeted mutation list and the fail reasons once, so the log, the alert and the failure message all read the same value.
  3. log_detect (io.kestra.plugin.core.log.Log) prints the count, the mutation ids and the fail reasons.
  4. guard (io.kestra.plugin.core.flow.If) branches on the targeted count. With none found, clean logs and the run ends.
  5. findings_alert (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) posts the wedged mutations on every detection, in dry run and in remediate mode.
  6. mode (io.kestra.plugin.core.flow.If) enters the remediation branch only when auto_remediate is true and dry_run is false. Otherwise report_only lists what would be killed and stops.
  7. approval_gate (io.kestra.plugin.core.flow.Pause) holds the run until a human resumes it with approved. apply runs the kill only when approved is true.
  8. remediation_kill_loop (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs KILL MUTATION WHERE database = ... AND table = ... AND mutation_id = ... once per targeted mutation.
  9. reverify re-reads system.mutations with the same filter. verify_result then fails the run through io.kestra.plugin.core.execution.Fail if any targeted mutation is still is_done = 0.

What you get

  • A check that reads the real mutation state from system.mutations, so it catches the exact mutations that retry forever and block the queue.
  • A kill that is safe to leave scheduled. It reports until an operator turns on remediation and approves each run.
  • A real verification step. The run passes only when the second read of system.mutations shows zero targeted mutations.
  • Slack alerts on the findings and on any failure.

Who it's for

  • Data platform engineers who run ClickHouse and hit mutations wedged by a bad ALTER expression.
  • DBAs who need to clear a failing mutation that is holding up every later mutation on a table.
  • Teams evaluating the ClickHouse plugin who want an example that reads a system table and runs gated, verified remediation.

Why orchestrate this with Kestra

ClickHouse has no scheduler, no approval step and no way to prove a KILL MUTATION took effect. Kestra adds the missing pieces. It schedules the sweep, gates the kill behind dry-run and approval, re-reads system.mutations to turn a hopeful kill into a verified one, applies the same connection settings to every task and posts a Slack alert the moment a step fails.

Tested end to end

Run on Kestra 2.0.5 against a ClickHouse 24.8 server. A MergeTree table of five rows was wedged with a real failing mutation (ALTER TABLE ... UPDATE amount = toUInt64(raw) where raw holds non numeric strings), confirmed stuck in system.mutations with is_done = 0 and a Cannot parse fail reason.

  • Detector can fail: a healthy table returned 0 targeted mutations, the same read after the wedge returned 1 (confirmed with clickhouse-client against system.mutations).
  • Dry run detected the failing mutation, alerted and left it in place (still is_done = 0 afterwards).
  • Remediate with approval ran KILL MUTATION and the re-read reported 0 remaining (1 before, 0 after in system.mutations).
  • The approval gate held. A run resumed with approved false killed nothing.
  • A read-only user turned the kill into a no-op, the re-read found the mutation still pending and the run failed and alerted.
  • An unreachable host failed the detect step and fired the Slack alert.

Prerequisites

A ClickHouse server reachable over the JDBC HTTP interface and a user that can read system.mutations and run KILL MUTATION on the target tables.

Secrets

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

Inputs

Name Type Default Description
clickhouse_url STRING jdbc:clickhouse://localhost:8123/default ClickHouse JDBC URL.
use_secret_auth BOOL true Read credentials from secrets. Set false only for inline test credentials.
database STRING default Database whose mutations are checked.
table STRING "" Optional single table filter. Empty checks every table.
stuck_minutes INT 30 Age threshold for a mutation with no fail reason.
include_stuck_without_failure BOOL false Also target slow mutations past the age threshold.
dry_run BOOL true Report without killing.
auto_remediate BOOL false Kill behind the approval gate when dry_run is false.

Quick start

  1. Set CLICKHOUSE_USERNAME, CLICKHOUSE_PASSWORD and SLACK_WEBHOOK_URL as namespace secrets.
  2. Import this flow.
  3. Run it with database pointed at a ClickHouse database. The default dry run reports the stuck or failing mutations.
  4. To kill, run again with dry_run false and auto_remediate true, then resume the paused execution with approved true.

How to extend

  • Set include_stuck_without_failure true to also clear mutations that have no fail reason but have not finished within stuck_minutes.
  • Fail the run as soon as detection finds anything, for a read-only drift check with no remediation.
  • Add a second window that only alerts, so a human sees a wedged mutation before it is killed.
  • Pair this with a check on the ALTER that submitted the mutation, so the bad expression is fixed and the mutation does not come back.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.