New to Kestra?
Use blueprints to kickstart your first workflows.
Flag Cassandra base tables at default_time_to_live = 0 in keyspaces meant to expire data, alert Slack, set a bounded TTL with ALTER TABLE and verify.
A Cassandra table created without default_time_to_live keeps that setting at 0, which means no expiry. For a reference table that is correct. For a time series, event log or cache keyspace it is a slow outage: every write lives forever, SSTables keep growing, so compaction and repair cost climb until a node runs out of disk. Nothing in the cluster warns you, because 0 is a legal and common value.
This blueprint reads system_schema.tables for the keyspaces you name as expiry-expected, flags every base table whose default_time_to_live is still 0 and alerts Slack. When you allow it, it sets a bounded default TTL with ALTER TABLE ... WITH default_time_to_live, one table at a time, then reads the schema again and fails the run if any flagged table still reads 0.
This blueprint was created by zkasuran.
default_time_to_live applies to NEW writes only. Setting it does not expire, delete or reclaim rows already on disk. It does not change the per-row TTL of existing data. The re-verify step is a schema check: it confirms system_schema.tables now shows the bounded value, not that any disk was freed. To age out data already written you still need a per-row UPDATE ... USING TTL or a delete, which this guard deliberately does not do.
The remediation loop runs one ALTER TABLE at a time (concurrencyLimit: 1). Concurrent schema changes in one keyspace race Cassandra's schema agreement and can leave nodes disagreeing on the schema. Both write gates (auto_remediate and dry_run) must be set before any ALTER TABLE runs.
scan_tables (io.kestra.plugin.cassandra.standard.Query, read only) runs SELECT keyspace_name, table_name, default_time_to_live FROM system_schema.tables WHERE keyspace_name IN (...) for the target_keyspaces you pass. The restriction is on the keyspace_name partition key, so the read needs no ALLOW FILTERING and never touches a system keyspace. system_schema.tables lists base tables only, so views and indexes are out of scope by construction.flagged (io.kestra.plugin.core.output.OutputValues) keeps the rows at default_time_to_live = 0 and builds both an ALTER TABLE statement per table and a readable name list. summary logs the counts.route (io.kestra.plugin.core.flow.If) branches on whether any table is flagged:clean logs that every base table in the target keyspaces has a non-zero default TTL.alert posts the table list 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 each ALTER TABLE ... WITH default_time_to_live in turn.reverify re-reads system_schema.tables. verify_result fails the run through io.kestra.plugin.core.execution.Fail if any flagged table still reads 0.errors block alerts Slack when the flow itself fails, for example an unreachable cluster or a role without ALTER rights.daily Schedule trigger is shipped disabled: true and always forces safe mode.CREATE TABLE without a default TTL is an easy and expensive miss.system_schema.tables audit that also drives ALTER TABLE.A one-off DESCRIBE in cqlsh answers the question for one table at one moment and fixes nothing. Kestra reads the whole set on a schedule, turns it into a Slack page and an execution history, applies the fix only behind auto_remediate and dry_run, one statement at a time, then verifies the result by reading the schema back. The guard becomes a recurring check instead of a thing someone remembers to run.
On Kestra 2.0.5 OSS against Cassandra 5.0 (cassandra:5) on the same Docker network, with a local HTTP sink standing in for Slack. Seeded schema:
metrics_clean (expiry-expected): table rollup_daily created WITH default_time_to_live = 86400;metrics (expiry-expected): table readings created without a TTL, so default_time_to_live = 0;events (expiry-expected): table audit_log created without a TTL, so default_time_to_live = 0.| Run | Inputs | Result |
|---|---|---|
| 1 | target_keyspaces: [metrics_clean] |
Clean. Its one base table has a non-zero default TTL. No alert |
| 2 | target_keyspaces: [metrics, events] |
2 base tables flagged (metrics.readings, events.audit_log). Alert sent. system_schema.tables still shows 0 |
| 3 | run 2 keyspaces, auto_remediate: true, dry_run: true |
Same report. cqlsh confirms both still read 0. No ALTER ran |
| 4 | run 2 keyspaces, auto_remediate: true, dry_run: false, default_ttl_seconds: 2592000 |
Both ALTER TABLE ran one at a time. reverify found 0 still at 0. cqlsh confirms both now read 2592000 |
| 5 | cassandra_host pointed at an unreachable name |
scan_tables failed, the errors block posted to the sink, execution FAILED |
Before and after values from the cluster's own CLI are in the PR description.
system_schema.tables. For remediation, a role with ALTER permission on the target tables.docker run -d --name cassandra -e MAX_HEAP_SIZE=1G -e HEAP_NEWSIZE=256M cassandra:5, connect it to the Kestra network, then create a keyspace and a table without a TTL. Allow a minute or two for the single node to accept CQL.SLACK_WEBHOOK_URL: Slack incoming webhook used by alert and the errors block.username and password to each task's session from {{ secret(...) }} values rather than hardcoding them.SECRET_, for example SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
cassandra_host |
cassandra |
Contact point hostname or IP |
cassandra_port |
9042 |
Native CQL port |
cassandra_datacenter |
datacenter1 |
Local datacenter for the driver |
target_keyspaces |
[events] |
Keyspaces whose base tables are expected to expire data |
default_ttl_seconds |
2592000 |
Bounded default TTL applied on remediation (30 days) |
auto_remediate |
false |
Allow ALTER TABLE |
dry_run |
true |
Must be false for a statement to run |
SLACK_WEBHOOK_URL secret.target_keyspaces to the keyspaces whose tables are meant to expire data.default_ttl_seconds with the owners, then run with auto_remediate: true and dry_run: false.daily trigger, which always runs in safe mode.Pause before the loop for a human approval step on production clusters.gc_grace_seconds is 0, which risks resurrecting deleted rows.UPDATE ... USING TTL to age out rows written before the default TTL was set.