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

Cassandra Missing Default TTL Growth Guard

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.

Categories
DataInfrastructure

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.

Safety

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.

How it works

  1. 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.
  2. 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.
  3. route (io.kestra.plugin.core.flow.If) branches on whether any table is flagged:
    • clean: clean logs that every base table in the target keyspaces has a non-zero default TTL.
    • flagged: 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.
  4. The errors block alerts Slack when the flow itself fails, for example an unreachable cluster or a role without ALTER rights.
  5. The daily Schedule trigger is shipped disabled: true and always forces safe mode.

What you get

  • A standing answer to which base tables in your expiry keyspaces have no default TTL, read from Cassandra's own schema rather than guessed.
  • A bounded default TTL applied only behind two gates, one table at a time, with a re-read of the schema as proof.
  • A Slack page the moment an unbounded table appears, plus a separate failure channel when the guard cannot run.
  • A clear line between a schema fix (what this does) and reclaiming existing rows (what it does not).

Who it's for

  • Platform and database reliability teams running self-managed Cassandra who treat unbounded disk growth as an outage waiting to happen.
  • Teams onboarding new keyspaces where a CREATE TABLE without a default TTL is an easy and expensive miss.
  • Anyone evaluating the Cassandra plugin who wants a worked system_schema.tables audit that also drives ALTER TABLE.

Why orchestrate this with Kestra

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.

Tested end to end

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:

  • keyspace metrics_clean (expiry-expected): table rollup_daily created WITH default_time_to_live = 86400;
  • keyspace metrics (expiry-expected): table readings created without a TTL, so default_time_to_live = 0;
  • keyspace 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.

Prerequisites

  • A reachable Cassandra cluster with its native CQL port open to the Kestra Worker. For the audit, any authenticated role can read system_schema.tables. For remediation, a role with ALTER permission on the target tables.
  • Local testing: 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.

Secrets

  • SLACK_WEBHOOK_URL: Slack incoming webhook used by alert and the errors block.
  • This flow connects without a username or password, matching this repo's existing Cassandra blueprints against an unauthenticated local instance. For an authenticated cluster, add username and password to each task's session from {{ secret(...) }} values rather than hardcoding them.
  • In Kestra OSS, provide a secret as a base64-encoded environment variable prefixed SECRET_, for example SECRET_SLACK_WEBHOOK_URL.

Inputs

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

Quick start

  1. Add the SLACK_WEBHOOK_URL secret.
  2. Import the flow and set target_keyspaces to the keyspaces whose tables are meant to expire data.
  3. Run it once with the defaults and read which base tables have no default TTL.
  4. Agree a bounded default_ttl_seconds with the owners, then run with auto_remediate: true and dry_run: false.
  5. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Add a Pause before the loop for a human approval step on production clusters.
  • Set a different TTL per keyspace from a map, for example longer for audit tables than for metrics.
  • Also flag tables whose gc_grace_seconds is 0, which risks resurrecting deleted rows.
  • Pair with a separate flow that runs UPDATE ... USING TTL to age out rows written before the default TTL was set.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.