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

MariaDB System-Versioned History Purge Guard

Find MariaDB system-versioned tables whose expired history passes a threshold, alert Slack, purge it with DELETE HISTORY BEFORE SYSTEM_TIME and re-check.

Categories
DataInfrastructure

MariaDB system versioning (temporal tables, 10.3 and later) keeps a copy of every row an UPDATE or DELETE replaces. The old versions stay in the same table as history rows, each carrying the system-time period during which it was current. That history is what makes SELECT ... FOR SYSTEM_TIME and point-in-time queries work, but MariaDB never trims it. On a table under steady write traffic the history keeps growing while the current row count stays flat, so storage climbs and every versioned scan reads more dead rows.

This blueprint finds the system-versioned tables in a schema, counts the history rows whose period ended before a retention cutoff you set and reports the tables over a threshold. When you allow it, it runs DELETE HISTORY FROM <table> BEFORE SYSTEM_TIME '<cutoff>' one table at a time, which removes only the expired history and leaves current rows and newer history in place. It then counts the expired history again with the same cutoff and fails the run if any flagged table is still over the threshold.

This blueprint was created by zkasuran.

Findings

Finding Meaning
Expired history over threshold A system-versioned table holds more history rows ended before the cutoff than history_rows_threshold. Every FOR SYSTEM_TIME scan reads them and they hold storage until purged.

Safety

The guard stays read-only until auto_remediate is true and dry_run is false. It only ever touches tables whose information_schema.TABLES.TABLE_TYPE is SYSTEM VERSIONED, so a plain table is never read with FOR SYSTEM_TIME and never purged. DELETE HISTORY ... BEFORE SYSTEM_TIME removes only history rows whose period ended before the cutoff, so current data is out of reach by construction. The cutoff is resolved once at the start of the run and reused by the count, the purge and the re-check, so the three always agree. The purge runs one table at a time. The re-check reads the counts a second time, so a purge that did not land fails the run.

How it works

  1. compute_cutoff (io.kestra.plugin.jdbc.mariadb.Query, read-only) resolves the retention cutoff as now minus retention_days once, so the count, the purge and the re-check all use the same instant.
  2. list_versioned (io.kestra.plugin.jdbc.mariadb.Query, read-only) reads information_schema.TABLES for tables whose TABLE_TYPE is SYSTEM VERSIONED, joins information_schema.PERIODS to find each table's period end column (the implicit column is row_end) and returns a per-table count fragment that counts history rows ended before the cutoff.
  3. build_count joins the fragments into one UNION ALL. count_expired (io.kestra.plugin.jdbc.mariadb.Query, read-only) runs it and keeps only the tables whose expired history exceeds history_rows_threshold. summary and report log the result.
  4. route_flagged (io.kestra.plugin.core.flow.If) branches on whether anything is flagged.
    • clean: logs that nothing is over the threshold.
    • flagged: alert posts the tables to Slack. remediate_gate runs only when auto_remediate is true and dry_run is false.
      • remediate (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs DELETE HISTORY FROM <table> BEFORE SYSTEM_TIME '<cutoff>' one flagged table at a time.
      • reverify counts expired history again with the same cutoff and verify_result fails the run if any flagged table is still over the threshold.
  5. errors posts a Slack alert when any task fails, for example when the database is unreachable.

What you get

  • The system-versioned tables whose history has outgrown the retention window, with the expired row count for each one.
  • A read-only default. Nothing is deleted until you set auto_remediate true and dry_run false.
  • A purge that removes expired history only, so current rows and in-window history are never touched.
  • A second count with the same cutoff as proof, so a purge that did not land fails the run.

Who it's for

  • Teams running MariaDB system-versioned tables whose storage keeps climbing while the current row count is flat.
  • DBAs who trim history by hand and want it on a schedule with a safe default and an audit trail.
  • Teams trying the MariaDB plugin who want an example that reads information_schema and drives maintenance from it.

Why orchestrate this with Kestra

MariaDB does not expire system-versioning history for you. DELETE HISTORY has no built-in schedule, no alert and no check that it worked. Running it by hand means someone has to remember, pick a safe cutoff, hold it steady across the count and the delete, then confirm the history actually fell. Kestra runs the scan on a schedule, sends the tables over the threshold to Slack, keeps the delete behind two inputs, purges one table at a time, re-checks with the same cutoff and keeps a history of every run.

Tested end to end

Tested on Kestra 2.0.5 OSS against MariaDB 11.8 (schema appdb), with the shared HTTP sink standing in for Slack. The schema held two system-versioned tables, orders (implicit row_end period column) and accounts (explicit ve period column), plus one plain table plain_logs that must never be touched. History was generated with UPDATE and DELETE, then COUNT(*) FOR SYSTEM_TIME ALL WHERE <end> < '<cutoff>' was read directly in the MariaDB client before and after each run.

Run Inputs Result
1 threshold 100 SUCCESS (6YbiqYePMKH4YqVfEK5zMD). 2 versioned tables found, 0 over the threshold (orders 5, accounts 3). Clean branch. CLI counts unchanged
2 threshold 2 SUCCESS (5giHiegqVkiZ374yi77SBg). 2 tables flagged (orders 5, accounts 3). Slack alert sent (HTTP 200). No purge, CLI counts stayed 5 and 3
3 threshold 2, auto_remediate: true, dry_run: true SUCCESS (42hr7hE3LYvvsICwn0BgnB). Same report. dry_run held the purge back, CLI counts stayed 5 and 3
4 threshold 2, auto_remediate: true, dry_run: false SUCCESS (1JGtBfBbfP3MHtQVvGHjHK). DELETE HISTORY on both tables, re-check clean. CLI expired counts went 5 to 0 and 3 to 0, current rows unchanged at 2 and 1, plain_logs untouched
5 unreachable database FAILED (Yp6sookFOPw5XPOjMfCkB). compute_cutoff could not connect, so the errors block posted the failure alert to Slack (HTTP 200)

Prerequisites

  • A reachable MariaDB 10.3 or later instance and its JDBC URL in jdbc_url.
  • A user that can read information_schema and the versioned tables. For remediation the user can run DELETE HISTORY (the DELETE privilege) on the flagged tables.
  • A schema that uses system-versioned tables. A table is versioned when it was created WITH SYSTEM VERSIONING.

Secrets

  • MARIADB_PASSWORD: password for the MariaDB user. The password input defaults to it.
  • SLACK_WEBHOOK_URL: used by alert and the errors block.
  • In Kestra OSS, provide each one as a base64-encoded environment variable prefixed SECRET_.

Inputs

Input Default Purpose
jdbc_url jdbc:mariadb://mariadb:3306/mydb MariaDB instance to connect to
username root MariaDB user
password {{ secret('MARIADB_PASSWORD') }} Password for that user
target_schema mydb Schema whose versioned tables are scanned
retention_days 30 History older than this is expired (cutoff is now minus this)
history_rows_threshold 100000 Flag a table over this many expired history rows
auto_remediate false Allow DELETE HISTORY
dry_run true Must be false for a purge to run

Quick start

  1. Add the MARIADB_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Import the flow and set jdbc_url, username and target_schema.
  3. Run it once with the defaults and read the flagged tables and their expired history counts.
  4. Set retention_days to the window you want to keep.
  5. When you are ready to reclaim, run with auto_remediate: true and dry_run: false.
  6. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Lower retention_days on schemas where history grows fast or raise it where you need a longer audit window.
  • Add a Pause before remediate so a human approves the purge window on busy tables.
  • Drive a per-table retention from a map instead of one window for the whole schema.
  • Point the flow at several schemas or instances to sweep a whole fleet.
  • Replace the Slack alert with PagerDuty, Microsoft Teams or email.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.