New to Kestra?
Use blueprints to kickstart your first workflows.
Find MariaDB system-versioned tables whose expired history passes a threshold, alert Slack, purge it with DELETE HISTORY BEFORE SYSTEM_TIME and re-check.
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.
| 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. |
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.
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.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.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.route_flagged (io.kestra.plugin.core.flow.If) branches on whether anything is 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.errors posts a Slack alert when any task fails, for example when the database is
unreachable.auto_remediate true and dry_run
false.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 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) |
jdbc_url.information_schema and the versioned tables. For remediation the user
can run DELETE HISTORY (the DELETE privilege) on the flagged tables.WITH SYSTEM VERSIONING.MARIADB_PASSWORD: password for the MariaDB user. The password input defaults to it.SLACK_WEBHOOK_URL: used by alert and the errors block.SECRET_.| 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 |
MARIADB_PASSWORD and SLACK_WEBHOOK_URL secrets.jdbc_url, username and target_schema.retention_days to the window you want to keep.auto_remediate: true and dry_run: false.daily trigger, which always runs in safe mode.retention_days on schemas where history grows fast or raise it where you need a
longer audit window.Pause before remediate so a human approves the purge window on busy tables.