New to Kestra?
Use blueprints to kickstart your first workflows.
Flag ScyllaDB base tables below a gc_grace_seconds floor that risk zombie data, alert Slack, raise it with ALTER TABLE and verify the schema.
A ScyllaDB table deletes a row by writing a tombstone, a marker that says the row is gone. gc_grace_seconds is how long that tombstone is kept before compaction may purge it. The window has to outlast the time a repair needs to carry the deletion to every replica. Set it too low and the worst case is 0, so a replica that was down during the delete can miss the tombstone. After the tombstone is purged that replica hands the old row back when it rejoins. The deleted row returns. People call it zombie data. Nothing in the cluster warns you, because a low or zero value is legal and is sometimes chosen on purpose for a table that never deletes.
This blueprint reads system_schema.tables for the keyspaces you name as delete-heavy, flags every base table whose gc_grace_seconds is below a floor you set and alerts Slack. When you allow it, it raises the setting with ALTER TABLE ... WITH gc_grace_seconds, one table at a time, then reads the schema again and fails the run if any flagged table still reads below the floor. It reaches ScyllaDB over the bundled Cassandra plugin (io.kestra.plugin.cassandra.standard.Query), because ScyllaDB speaks the Cassandra CQL wire protocol, so no extra plugin install is needed.
This blueprint was created by zkasuran.
gc_grace_seconds is a schema setting. Raising it changes how long future tombstones are kept. It does not run a repair. It does not reconcile replicas or undo a resurrection that already happened. The re-verify step reads system_schema.tables again and confirms the setting now sits at or above the floor. That is a check on the schema, not proof that any data was repaired. To close the window for good you still run a full repair inside gc_grace_seconds on a schedule, which this guard deliberately does not do.
The remediation loop runs one ALTER TABLE at a time (concurrencyLimit: 1). Concurrent schema changes race ScyllaDB schema agreement and can leave nodes disagreeing on the schema. Both write gates (auto_remediate true with dry_run false) must be set before any ALTER TABLE runs.
scan_tables (io.kestra.plugin.cassandra.standard.Query, read only) runs SELECT keyspace_name, table_name, gc_grace_seconds 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 stay out of scope.flagged (io.kestra.plugin.core.output.OutputValues) keeps the rows below the floor and builds one ALTER TABLE statement per table plus a readable name list. summary logs the counts.route (io.kestra.plugin.core.flow.If) branches on whether any table is below the floor:clean logs that every base table in the target keyspaces is at or above the floor.alert posts the table list to Slack.remediate_gate runs only when auto_remediate is true with dry_run false.remediate (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs each ALTER TABLE ... WITH gc_grace_seconds through io.kestra.plugin.cassandra.standard.Query in turn.settle waits a few seconds for schema agreement.reverify re-reads system_schema.tables. verify_result fails the run through io.kestra.plugin.core.execution.Fail if any flagged table still reads below the floor.errors block alerts Slack when the flow itself fails, for example an unreachable cluster or a role without ALTER rights.daily Schedule trigger ships disabled: true and always forces safe mode.gc_grace_seconds, read from ScyllaDB's own schema rather than guessed.CREATE TABLE with a low or zero gc_grace_seconds is an easy 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 with 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 against ScyllaDB (scylladb/scylla) on the same Docker network, with a local HTTP sink standing in for Slack. Seeded schema:
metrics_clean: table rollup_daily left at the default gc_grace_seconds = 864000;metrics: table readings created WITH gc_grace_seconds = 60;events: table audit_log created WITH gc_grace_seconds = 0.| Run | Inputs | Result |
|---|---|---|
| 1 | target_keyspaces: [metrics_clean], floor 3600 |
Clean. Its one base table reads 864000. No alert |
| 2 | target_keyspaces: [events, metrics], floor 3600 |
2 base tables flagged (events.audit_log at 0, metrics.readings at 60). Alert sent. Schema still reads 0 and 60 |
| 3 | run 2 keyspaces, auto_remediate: true, dry_run: true |
Same report. cqlsh confirms both unchanged. No ALTER ran |
| 4 | run 2 keyspaces, auto_remediate: true, dry_run: false |
Both ALTER TABLE ran one at a time. reverify found 0 below the floor. cqlsh confirms both now read 3600 |
| 5 | scylla_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 on the target tables.io.kestra.plugin.cassandra.standard.Query). ScyllaDB speaks the Cassandra CQL wire protocol, so this plugin ships with Kestra and needs no separate install.docker run -d --name scylla --network <kestra-network> scylladb/scylla --smp 1 --memory 1G --overprovisioned 1 --developer-mode 1, then create a keyspace and a table with a low gc_grace_seconds. 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 plus a password: {{ secret('SCYLLA_PASSWORD') }} to each task's session rather than hardcoding them. The driver needs both or neither.SECRET_, for example SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
scylla_host |
scylla |
Contact point hostname or IP |
scylla_port |
9042 |
Native CQL port |
scylla_datacenter |
datacenter1 |
Local datacenter for the driver |
target_keyspaces |
[events] |
Keyspaces whose base tables delete rows |
gc_grace_floor_seconds |
3600 |
Lowest gc_grace_seconds a base table may carry |
auto_remediate |
false |
Allow ALTER TABLE |
dry_run |
true |
Must be false for a statement to run |
SLACK_WEBHOOK_URL secret. The Cassandra plugin that reaches ScyllaDB ships with Kestra, so there is nothing extra to install.target_keyspaces to the keyspaces whose tables delete rows.gc_grace_floor_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.default_time_to_live is 0 in keyspaces meant to expire data.gc_grace_seconds, which is what actually closes the resurrection window.