Neo4j Long-Running Transaction Killer Guard
Find Neo4j transactions over an elapsed-time threshold, alert Slack, terminate the offenders after approval, then re-list and fail if any targeted transaction is still open.
Purge expired Neo4j session and event nodes with approval, bounded transactions and protected graph checks.
An expiry property is a deletion contract. A wrong unit or an overbroad policy can destroy live graph data. Use integer Unix seconds, review the policy and keep a restorable backup.
| Guard | What it stops |
|---|---|
dry_run: true, auto_remediate: false and explicit approval |
An unattended run deleting data |
| Every node label and every attached relationship type must be allowed | Removing business nodes or edges outside the retention contract |
| Fixed past cutoff, integer expiry and degree ceiling | Future timestamps, malformed expiry and high-degree deletes |
| Whole-plan node budget and bounded transactions | Silent truncation and an oversized delete transaction |
| Exact plan recheck after approval | Applying an approval after nodes, expiry values or edges changed |
Node property lock, approved edge subset and plain DELETE |
Deleting an extended node or silently detaching an unexpected edge |
| Recount, approved-id absence and protected census | Reporting success with targets remaining or protected counts changed |
clock (io.kestra.plugin.neo4j.Query) fixes the cutoff from database time or a supplied past value. audit counts expired and eligible nodes per policy label. Missing, non-integer and equal-to-cutoff expiry values are retained.plan (io.kestra.plugin.neo4j.Query) records element ids, expiry values and sorted incident edge ids. All labels on a target must belong to retention_labels; all edges must use disposable_relationship_types. High-degree nodes are excluded. within_budget (io.kestra.plugin.core.execution.Fail) refuses a plan over max_nodes before approval or writes.purge_gate (io.kestra.plugin.core.flow.If) requires both mutation inputs. approval (io.kestra.plugin.core.flow.Pause) waits up to 20 minutes. approved_purge reads onResume.approved ?? false; fresh_plan and unchanged_plan refuse a changed snapshot.batches (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs native delete_batch queries. Each transaction locks node expiry values before rechecking them. A batch whose target lost eligibility, changed expiry or gained an unexpected edge deletes nothing. Approved edges already removed by an earlier batch are allowed to be absent. The query explicitly deletes the captured disposable edges then uses plain node DELETE; it never uses DETACH DELETE.remaining, approved_remaining and protected_after (io.kestra.plugin.neo4j.Query) check eligible backlog, target absence and counts of labels and relationship types outside the policy. verified fails on a mismatch. failure_alert (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) reports errors.A repeatable expiry sweep with per-label counts, protected lookalikes and bounded irreversible changes. Expired nodes excluded by the policy remain in the report; a successful purge clears eligible nodes at the original cutoff.
Platform teams whose Neo4j session or event loader writes an expiry property and needs an approved retention sweep that respects the rest of the graph.
Kestra keeps the database snapshot, approval, native writes and verification in one execution history. The disabled nightly schedule runs with safe inputs. A flow concurrency limit serializes executions of this flow id.
Kestra 2.0.6 OSS with bundled plugin-neo4j 1.6.0 and Neo4j Community 5.26.32. Independent cypher-shell reads measured 6 eligible expired nodes before remediation and 0 afterward, using batches of 2. All 28 retained nodes, their identities and properties and all 3 non-disposable relationships survived. The disposable edge count fell by 1.
| Case | Execution | Result |
|---|---|---|
| healthy | 30s49ymgn6g9uJtbeUlrHK |
SUCCESS |
| degraded_dry_run | HokYIFVPIMENmykWpLJFf |
SUCCESS |
| declined_approval | 7OyqCIz2M8cBmiOcNsB8cW |
SUCCESS |
| remediation | 4XP0915Ahqa8KRb3V64MZp |
SUCCESS |
| repeat | 7kLP23ZeAqDQNqStwuUKbN |
SUCCESS |
| ineffective_write | 5ZthdnArumFx4bjJ26TgUf |
FAILED |
| race_or_partial | 44PMyoOWTargdQUnWqADOg |
FAILED |
The refused write held database locks until the native transaction failed. No target was removed. Further cases cover budget equality and excess, cutoff equality, degree boundaries, an empty graph, 1001 candidates, quoted Unicode identifiers, a concurrent execution held by the flow limit, changes during approval, a disconnected service, partial recovery and a repeat run. Healthy, remediation and stale-approval cases were repeated on a fresh fixture.
| Secret | Purpose |
|---|---|
NEO4J_URL |
Default Bolt endpoint |
NEO4J_PASSWORD |
Default Neo4j credential |
SLACK_WEBHOOK_URL |
Default failure webhook through secret('SLACK_WEBHOOK_URL') |
| Input | Default | Purpose |
|---|---|---|
neo4j_url, neo4j_username, neo4j_password |
URL secret, neo4j, password secret |
Connection and complete graph visibility |
retention_labels |
Session,Event |
All labels allowed on a deletable node |
expiry_property |
expires_at |
Integer Unix seconds, strictly below cutoff |
disposable_relationship_types |
NEXT_EVENT |
Edge types explicitly allowed to be deleted |
cutoff_epoch |
0 |
Database time or an explicit past cutoff |
max_nodes |
500 |
Whole-plan budget, at most 1000 |
batch_size |
50 |
Nodes per transaction, at most 100 |
max_degree |
10 |
Incident edge ceiling per target, at most 20 |
auto_remediate, dry_run |
false, true |
Both must permit mutation |
slack_webhook_url |
Slack secret | Failure webhook |
retention_report.auto_remediate: true and dry_run: false, review the snapshot then approve the Pause.Add an export-and-restore procedure before deletion if retention requires an archive. For a large graph, replace the dynamic scan with explicit indexed label/property branches and repeat the detector and race cases before increasing budgets.
Limits: this is a logical retention purge. The dynamic queries scan the graph; node budgets bound selected output and writes, not scan cost. It does not compact store files or prove disk-space, cache or latency gains. Missing, malformed, future, non-disposable and high-degree records remain. Cross-flow copies and application writers have separate concurrency scopes. Concurrent unrelated changes can fail protected-census verification after earlier batches committed. A failed or interrupted run keeps completed transactions; the next run plans remaining eligible records without restoring deleted data. Use server timeouts to bound database work as well as the task timeout. The neo4j.Query task of plugin-neo4j 1.4.9 (bundled with Kestra 2.0.5) has no database or parameters property, so the queries run on the account's home database and the label, property and relationship names are placed in the Cypher text as escaped string literals. Set the home database of the account if the data is not in the default one.
This blueprint was created by zkasuran.
