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

Neo4j Index Population Failure Recovery Guard

Find Neo4j indexes left FAILED or stuck POPULATING, alert Slack, drop and recreate each from its own definition, then verify every index is ONLINE.

Categories
DataInfrastructure

A Neo4j schema index does not always come up ONLINE. A population error, such as a property value too large to index, leaves it FAILED. A crash during the build can leave it stuck POPULATING. Either way the index stays in the catalog and is still maintained on every write, but it never serves a lookup. Queries that should use it fall back to a full label scan and get slower, with nothing in an error log to say why. Neo4j does not recover a FAILED index on its own, even once the cause is gone: it stays FAILED until someone drops and recreates it.

This blueprint lists every index that is not ONLINE. It alerts Slack and can drop then recreate each one from the index own createStatement, so the rebuild uses Neo4j exact definition for any index type. It waits for population to finish, then audits again and fails the run if an index it rebuilt is still not ONLINE.

This blueprint was created by zkasuran.

Findings

State What it means What this guard does
FAILED Population hit an error and gave up. Still written on every change, never read Drop and recreate from its own definition, then verify ONLINE
POPULATING Still building or stuck mid build after crash Same path. A legitimate build in progress also shows this, so run the guard outside large index builds
owned by a constraint A non-ONLINE index that backs a constraint Reported, not touched. DROP INDEX refuses it, the constraint must be rebuilt instead

How it works

  1. audit (io.kestra.plugin.neo4j.Query, read-only, storeType: FETCH) runs SHOW INDEXES ... WHERE state <> 'ONLINE'. For each index it returns the state, the type, whether it is remediable (not owned by a constraint), the failure message, a DROP INDEX ... IF EXISTS statement with the name back-quoted and the index own createStatement.
  2. summary logs every finding with its exact definition.
  3. route (io.kestra.plugin.core.flow.If on outputs.audit.size > 0):
    • clean: clean logs that every index is ONLINE.
    • findings: alert posts the list to Slack, noting any constraint-owned indexes it will not touch and whether it is about to remediate.
      • remediate_gate runs only when auto_remediate is true and dry_run is false.
      • remediate (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) takes one index at a time. For a remediable index it runs the drop then the recreate. A constraint-owned index is skipped.
      • settle calls db.awaitIndexes to wait for the recreated indexes to build.
      • reaudit runs the same SHOW INDEXES check again. verify_result fails the run if any index this run tried to remediate is still not ONLINE.
  4. errors alerts Slack when the flow itself fails, for example an unreachable instance or bad credentials, so a failed audit is never read as "no problem".

What you get

  • A direct list of the indexes Neo4j is maintaining but never using, from its own SHOW INDEXES state, not guessed from query plans.
  • A rebuild that uses each index own definition, so range, text, point, full-text and lookup indexes all come back with the right type and options.
  • A guard that never drops a constraint-backed index by mistake, because DROP INDEX refuses those and the flow filters them out first.
  • Two independent gates (auto_remediate, dry_run) between a finding and any schema change.
  • Proof the fix landed: the run fails loudly if a rebuilt index did not reach ONLINE, so a stubborn root cause is surfaced rather than hidden.

Who it's for

  • Teams running Neo4j that add indexes through migrations, where a single failed build leaves a silent gap in coverage.
  • Platform and data engineers who have seen a query slow down for no visible reason and traced it to an index that was never ONLINE.
  • Anyone evaluating the Neo4j plugin who wants a worked example of Query driving schema introspection (SHOW INDEXES) and schema change (DROP INDEX, CREATE INDEX) in one flow.

Why orchestrate this with Kestra

Spotting a non-ONLINE index is one query. Fixing it safely is a sequence: capture each index real definition, drop and recreate one at a time, wait for population, then check the result and fail if it did not take. Kestra runs that sequence on a schedule behind two gates, keeps constraint-backed indexes out of it, alerts Slack on both the finding and a failure, then keeps an execution history of what was rebuilt and where.

Tested end to end

On Kestra 2.0.5 OSS against Neo4j 5.26.31 Community, with a local HTTP sink standing in for Slack. A FAILED index was produced for real by indexing an oversized property: a Doc node was given a 200000 character blob, then CREATE INDEX ... FOR (n:Doc) ON (n.blob) drove the index to FAILED (property value too large to index). cypher-shell confirmed the state before and after every run.

Run Inputs Result
1 defaults, clean graph 0 indexes not ONLINE, clean logged. No alert
2 defaults, one FAILED index 1 index reported, alert sent. No change. cypher-shell shows it still FAILED
3 auto_remediate: true, dry_run: true Same report, remediation held. cypher-shell shows the index untouched, still FAILED
4 auto_remediate: true, dry_run: false, root cause fixed first Dropped and recreated. settle waited, reaudit clean, verify_result passed. cypher-shell shows the index ONLINE
5 auto_remediate: true, dry_run: false, oversized value still present Dropped and recreated, population FAILED again, verify_result failed the run with the index name and state. errors alert sent
6 unreachable URL audit failed, errors alert sent

Exact execution ids and the before and after state counts are in the pull request notes.

Prerequisites

  • A reachable Neo4j 4.x or 5.x instance. SHOW INDEXES and its createStatement column are available on Community and Enterprise.
  • For the audit, any user that can run SHOW INDEXES. For remediation, a user allowed to DROP INDEX and CREATE INDEX.
  • A Slack incoming webhook for alerts.
  • Local testing:
    • docker run -d --name neo4j -p 7687:7687 -e NEO4J_AUTH=neo4j/testpassword neo4j:5;
    • create a node with an oversized property then index it to force a FAILED index: CREATE (:Doc {blob: 'x'}); MATCH (n:Doc) SET n.blob = reduce(s='', i IN range(1,20000) | s + 'ABCDEFGHIJ'); CREATE INDEX doc_blob_idx FOR (n:Doc) ON (n.blob);.

Secrets

  • NEO4J_PASSWORD: password for the Neo4j user, the default for the password input.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used by alert and the errors block.
  • In Kestra OSS, provide each one as a base64-encoded environment variable prefixed SECRET_, for example SECRET_NEO4J_PASSWORD=$(echo -n 'testpassword' | base64).

Inputs

Input Default Purpose
url bolt://localhost:7687 Bolt endpoint
user neo4j Neo4j user
password {{ secret('NEO4J_PASSWORD') }} Password, from the secret by default
await_timeout_seconds 120 How long db.awaitIndexes waits for a rebuild
auto_remediate false Allow drop and recreate
dry_run true Must be false for any schema change to run

Quick start

  1. Add the NEO4J_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Import the flow and set url and user.
  3. Run it once with the defaults and read the indexes that are not ONLINE.
  4. Fix the root cause of each failure first, for example remove or shrink a property value too large to index.
  5. Run it with auto_remediate: true and dry_run: false to drop and recreate the indexes, outside peak hours since a rebuild reads the whole label.
  6. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Add a Pause before remediate for human approval on production graphs.
  • Narrow the audit to one database with SHOW INDEXES run against a specific database in a multi-database setup.
  • Loop over several Bolt URLs to check every instance of a cluster.
  • Treat a long-running POPULATING as healthy by excluding indexes whose populationPercent is climbing, so only truly stuck ones alert.
  • Route constraint-owned findings to a separate flow that rebuilds the constraint.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.