New to Kestra?
Use blueprints to kickstart your first workflows.
Detect Schema Registry subjects with no live Kafka topic, hard delete them behind an approval gate, then re-list and fail if any targeted subject remains.
A Confluent Schema Registry under the TopicNameStrategy keeps one subject per topic field: <topic>-value for the record value and <topic>-key for the key. The subjects are not tied to the topic lifecycle. Drop the topic and its subjects stay registered, holding their schema ids and version history. Over time a cluster collects subjects for topics nobody runs any more. They clutter GET /subjects, they confuse a compatibility check that reads the latest version of a dead subject and they pin schema ids that can never be reused.
Soft deletes leave the same debris. DELETE /subjects/<subject> without permanent=true only hides the subject from the default listing. The schema ids stay allocated until a second call with permanent=true runs. That second call is easy to forget.
This blueprint lists the registered subjects, lists the already soft deleted ones, reads the live Kafka topics and works out which subjects are orphaned. It tracks how long each has been orphaned in a KV cursor, so a subject deletes only after its topic has been gone past a threshold. Deletion is off by default, gated behind auto_remediate, dry_run and a human approval. After the delete it re-lists the subjects and fails the run if any targeted subject is still there.
This blueprint was created by zkasuran.
auto_remediate true, dry_run false and a human resuming the Pause with approved=true. An unanswered resume reads as not approved.protected_subjects are never deleted, even when their topic is gone.<topic>-value or <topic>-key naming are matched against topics. A custom named subject is never treated as topic bound.freeze captures one timestamp for the run, so the detect, cursor and age steps all agree on now.list_subjects (io.kestra.plugin.core.http.Request) reads GET /subjects, the subjects served right now.list_all_subjects reads GET /subjects?deleted=true, which also returns the soft deleted ones. The soft deleted set is the difference between the two lists.list_topics (io.kestra.plugin.kafka.TopicList, read-only) returns the live topics on the cluster.analyze builds the orphan set. A subject is orphaned when it follows the topic name strategy and its topic is not in the live set or when hard_delete_soft_deleted is true and it is already soft deleted. Protected subjects are dropped.route (io.kestra.plugin.core.flow.If) branches on whether any orphan exists.clear_cursor resets the first seen record and all_clean logs that there is nothing to clean. No alert.cursor_merge and save_cursor keep the first time each still orphaned subject was seen in the KV key kafka_sr_orphans_<registry>, with no ttl.split separates the orphans past min_age_minutes from the ones held back as too recent.log_findings logs the split and alert posts it to Slack.remediate_gate runs only when auto_remediate is true, dry_run is false and at least one subject is past the threshold.approve (io.kestra.plugin.core.flow.Pause) waits for a resume carrying approved.remediate_delete (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) soft deletes then permanently deletes each targeted subject, reverify re-lists and verify_result fails the run if any targeted subject is still present.not_approved logs that nothing was deleted.errors posts to Slack on any failure, for example an unreachable registry or a subject that survived the delete.auto_remediate, dry_run and a human approval.DELETE ...?permanent=true against production by hand.TopicList paired with the registry REST API behind an approval gate.A one line curl can delete a subject. It cannot remember how long the subject has been orphaned, cross check the live topic set, hold the delete for a second person or prove afterwards that the subject is really gone. Kestra adds the schedule that watches the registry, the KV cursor that measures the orphaned age across runs, the Pause that turns a permanent delete into an approved change, the re-list that proves the result and a history of every cleanup.
On Kestra 2.0.5 OSS against Apache Kafka 3.9.1 (KRaft, single node) with Confluent Schema Registry 7.9.0 and a local HTTP sink standing in for Slack. Four topics (orders, payments, shipments, inventory) each had Avro subjects registered under the TopicNameStrategy, plus a non strategy subject my-custom-subject that must never be matched. State was confirmed with GET /subjects plus GET /subjects?deleted=true before and after each run.
| Run | Inputs | Result |
|---|---|---|
| 1 | defaults, clean registry | 0 orphans. All 6 subjects map to a live topic or are non strategy. Nothing deleted. No alert |
| 2 | topic shipments dropped, payments-value soft deleted, min_age_minutes: 0 |
2 orphans reported (shipments-value topic gone, payments-value soft deleted), both past threshold. dry_run true so nothing deleted. ?deleted=true unchanged |
| 3 | same, auto_remediate: true, dry_run: true |
Same report, no Pause, nothing deleted. Registry unchanged |
| 4 | min_age_minutes: 120, fresh cursor, auto_remediate: true, dry_run: false |
2 orphans reported, both held as too recent, 0 past threshold, no Pause. Registry unchanged |
| 5 | min_age_minutes: 0, auto_remediate: true, dry_run: false, approved |
Paused, resumed with approved=true. shipments-value soft then hard deleted, payments-value hard deleted. Re-list confirmed gone, ?deleted=true dropped from 6 subjects to 4 |
| 6 | topic inventory dropped, inventory-value set read only, approved |
Delete rejected by read only mode, re-list still showed inventory-value, run failed and alerted |
| 7 | registry URL unreachable | list_subjects failed, run failed, errors alert sent |
Across every run the subjects whose topic still exists (orders-value, orders-key) and the non strategy subject (my-custom-subject) were never flagged.
TopicNameStrategy (<topic>-value, <topic>-key). A different strategy needs the match in analyze adjusted.SLACK_WEBHOOK_URL: used by alert and the errors block. In Kestra OSS, provide it as the base64 encoded environment variable SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
schema_registry_url |
http://localhost:8081 |
Registry base URL, no trailing slash |
bootstrap_servers |
localhost:9092 |
Brokers, read the live topic set |
min_age_minutes |
1440 |
How long a subject must stay orphaned before it can be deleted |
hard_delete_soft_deleted |
false |
Also treat already soft deleted subjects as orphans |
protected_subjects |
[] |
Subject names never deleted |
auto_remediate |
false |
Allow the delete |
dry_run |
true |
Must be false for a delete to apply |
SLACK_WEBHOOK_URL secret.schema_registry_url plus bootstrap_servers.min_age_minutes to how long a topic must be gone before you trust the delete, for example a day.auto_remediate: true and dry_run: false, then resume the paused execution with approved=true.daily_scan trigger, which always runs in safe mode.schema_registry_url the target registry.hard_delete_soft_deleted to true to also reclaim the ids of subjects an operator soft deleted and forgot.analyze, for example RecordNameStrategy.