
Neo4j Query Tuning Skill
- 397 installs
- 101 repo stars
- Updated August 3, 2026
- neo4j-contrib/neo4j-skills
neo4j-query-tuning-skill is an agent skill that diagnoses slow Neo4j Cypher queries from execution plans and prescribes targeted index, planner, and monitoring fixes.
About
neo4j-query-tuning-skill is an agent skill that walks solo builders and small teams through Neo4j Cypher performance diagnosis end to end. It teaches when to use EXPLAIN versus PROFILE, how to read plans bottom-up, and which operators signal missing indexes, bad cardinality, or expensive graph traversals. The skill pairs operator-level fix strategies with planner hints and runtime selection guidance for slotted, pipelined, and parallel execution, including Enterprise-only capabilities where noted. It also covers operational tooling—SHOW QUERIES, SHOW TRANSACTIONS, transaction termination, and db.stats-style retrieval—so you can fix slow queries without guessing. Reference files deepen coverage of plan operators and stats/monitoring. Install via agentskills.io for Claude Code-style agents when your app depends on Neo4j and latency or cost spikes trace back to Cypher.
- EXPLAIN vs PROFILE with dbHits, rows, estimatedRows, and pageCacheHitRatio
- Full execution-plan operator reference with good/bad signals and fix strategies
- Cardinality estimation, stale stats, CartesianProduct, Eager, and over-traversal remedies
- Planner hints: USING INDEX, USING SCAN, USING JOIN ON; slotted, pipelined, parallel runtimes
- Query control via SHOW QUERIES, TERMINATE TRANSACTION; Aura and self-managed Neo4j 2025.x/2026.x
Neo4j Query Tuning Skill by the numbers
- 397 all-time installs (skills.sh)
- +35 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #148 of 911 Databases skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-query-tuning-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 397 |
|---|---|
| repo stars | ★ 101 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | neo4j-contrib/neo4j-skills ↗ |
What it does
Diagnose slow Cypher queries on production Neo4j with EXPLAIN/PROFILE and apply index, planner, and runtime fixes.
Who is it for?
Best when you're shipping features on Neo4j or Aura and need systematic Cypher tuning without a dedicated DBA.
Skip if: Skip if you have no running Neo4j instance or and only need one-off CRUD snippets without performance symptoms.
When should I use this skill?
Cypher latency, high dbHits, or bad plans appear on production or staging Neo4j workloads.
What you get
You get an interpreted execution plan, concrete remediation steps, and monitoring commands to validate improvements on Neo4j 2025.x/2026.x.
- Interpreted execution plan with operator-level diagnosis
- Targeted fix list (indexes, hints, replanning, runtime choice)
- Monitoring steps using SHOW QUERIES / stats APIs where available
By the numbers
- Complete execution-plan operator reference table in bundled references
- Targets Neo4j 2025.x / 2026.x (self-managed or Aura)
Files
When to Use
- Query takes unexpectedly long; need root-cause analysis
- EXPLAIN/PROFILE output in hand — needs interpretation
- Identifying which index is missing or unused
- Deciding between slotted / pipelined / parallel runtimes
- Monitoring live queries: SHOW QUERIES, SHOW TRANSACTIONS
- Cardinality estimates wrong (plan replanning needed)
When NOT to Use
- Writing Cypher from scratch →
neo4j-cypher-skill - GDS algorithm performance →
neo4j-gds-skill - Schema design / data modelling →
neo4j-modeling-skill
---
EXPLAIN vs PROFILE
| EXPLAIN | PROFILE | |
|---|---|---|
| Executes query? | No | Yes |
| Returns data? | No | Yes |
Shows rows (actual) | No | Yes |
Shows dbHits (actual) | No | Yes |
Shows estimatedRows | Yes | Yes |
| Cost | Zero | Full query cost |
Run PROFILE twice — first run warms page cache; second gives representative metrics.
EXPLAIN MATCH (p:Person {email: $email}) RETURN p.name
PROFILE MATCH (p:Person {email: $email}) RETURN p.nameQuery API alternative (no driver):
curl -X POST https://<host>/db/<db>/query/v2 \
-u <user>:<pass> -H "Content-Type: application/json" \
-d '{"statement": "EXPLAIN MATCH (p:Person {email: $email}) RETURN p.name", "parameters": {"email": "a@b.com"}}'---
Key Plan Metrics
| Metric | Good | Investigate if |
|---|---|---|
dbHits | Low; drops after index added | High relative to rows |
rows | Shrinks early in plan | Large until final operator |
estimatedRows | Close to rows | >10× divergence from actual |
pageCacheHitRatio | >0.99 | <0.90 (disk I/O bottleneck) |
pageCacheHits | High | — |
pageCacheMisses | Near 0 | Rising (page cache too small) |
Read plans bottom-up — leaf operators at bottom initiate data retrieval.
---
Operator Reference
| Operator | Good/Bad | Meaning | Fix |
|---|---|---|---|
NodeIndexSeek | ✓ | Exact match via RANGE/LOOKUP index | — |
NodeUniqueIndexSeek | ✓ | Unique constraint index hit | — |
NodeIndexContainsScan | ✓ | TEXT index CONTAINS / STARTS WITH | — |
NodeIndexScan | ~ | Full index scan (no predicate) | Add WHERE predicate or composite index |
NodeByLabelScan | ✗ | Scans all nodes of label | Add RANGE index on lookup property |
AllNodesScan | ✗✗ | Scans entire node store | Add label + index to MATCH |
Expand(All) | ~ | Traverse relationships from node | Normal; limit with LIMIT or WHERE |
Expand(Into) | ~ | Find rels between two matched nodes | Normal for known-endpoint joins |
Filter | ~ | Predicate applied after scan | Move predicate into WHERE with index |
CartesianProduct | ✗ | No join predicate between two MATCH | Add WHERE join or use WITH between MATCHes |
NodeHashJoin | ~ | Hash join on node IDs | Normal; planner chose hash join |
ValueHashJoin | ~ | Hash join on values | Normal; watch memory for large inputs |
EagerAggregation | ~ | Full aggregation (ORDER BY, count(*)) | Normal for aggregates |
Aggregation | ✓ | Streaming aggregation | — |
Eager | ✗ | Read/write conflict; materialises all rows | See Eager fix strategies below |
Sort | ~ | Full sort — O(n log n) | Add LIMIT before Sort; push LIMIT earlier |
Top | ✓ | Sort+Limit combined — O(n log k) | Preferred over Sort+Limit |
Limit | ✓ | Truncates rows early | Push as early as possible |
Skip | ~ | Offset pagination | Use keyset pagination on large graphs |
ProduceResults | — | Final output operator | Root of tree |
UndirectedRelationshipByIdSeekPipe | ~ | Lookup by relationship ID | Avoid id(r) — use elementId(r) |
Full operator reference → references/plan-operators.md
---
Diagnostic Workflow (Agent Runbook)
Step 1 — Baseline Plan
EXPLAIN <query>Scan output for AllNodesScan, NodeByLabelScan, CartesianProduct, Eager.
Step 2 — Check Indexes
SHOW INDEXES YIELD name, type, labelsOrTypes, properties, state
WHERE state = 'ONLINE'Find whether the label/property from the bad operator has an index.
Step 3 — Create Missing Index
// RANGE index for equality/range predicates:
CREATE INDEX person_email IF NOT EXISTS FOR (n:Person) ON (n.email)
// TEXT index for CONTAINS/ENDS WITH:
CREATE TEXT INDEX person_bio IF NOT EXISTS FOR (n:Person) ON (n.bio)
// Composite for multi-property lookup:
CREATE INDEX order_status_date IF NOT EXISTS FOR (n:Order) ON (n.status, n.createdAt)Wait for state = 'ONLINE' before measuring.
Step 4 — Profile After Fix
PROFILE <query>Compare dbHits and elapsed ms before/after. Target: NodeIndexSeek replaces scan operators.
Step 5 — Stale Statistics (if estimatedRows wildly off)
CALL db.prepareForReplanning()
// or resample a specific index:
CALL db.resampleIndex("person_email")
// or resample all outdated:
CALL db.resampleOutdatedIndexes()Config: dbms.cypher.statistics_divergence_threshold (default 0.75 — plan expires when stat changes >75%).
---
Fixing Common Plan Problems
Missing Index → NodeByLabelScan / AllNodesScan
// Force index hint when planner ignores it:
MATCH (p:Person {email: $email})
USING INDEX p:Person(email)
RETURN p.name
// Force label scan (sometimes faster for high selectivity):
MATCH (p:Person {email: $email})
USING SCAN p:Person
RETURN p.nameWrong Anchor — Planner Picks Wrong Starting Node
Reorder MATCH or use hints:
// Force join at specific node:
MATCH (a:Author)-[:WROTE]->(b:Book)-[:IN_CATEGORY]->(c:Category {name: $cat})
USING JOIN ON b
RETURN a.name, b.titleCartesianProduct — Two Unconnected MATCHes
// Bad (Cartesian product):
MATCH (a:Author {id: $aid})
MATCH (b:Book {id: $bid})
RETURN a.name, b.title
// Good (explicit join or WITH):
MATCH (a:Author {id: $aid})-[:WROTE]->(b:Book {id: $bid})
RETURN a.name, b.title
// Or: WITH between them to reset planning contextEager — Read/Write Conflict
Three strategies (pick simplest): 1. Add specific labels to MATCH nodes so planner distinguishes read/write sets 2. Collect-then-write: WITH collect(n) AS nodes UNWIND nodes AS n SET n.x = 1 3. CALL IN TRANSACTIONS: isolates each batch in its own transaction
CYPHER 25
MATCH (p:Person) WHERE p.score > 100
CALL (p) { SET p.tier = 'gold' } IN TRANSACTIONS OF 1000 ROWSExpensive CONTAINS / ENDS WITH
// Needs TEXT index (RANGE does NOT support these):
CREATE TEXT INDEX person_bio IF NOT EXISTS FOR (n:Person) ON (n.bio)
MATCH (p:Person) WHERE p.bio CONTAINS $keyword RETURN p.nameOver-Traversal — Push LIMIT Early
// Bad: LIMIT after expensive join
MATCH (a:Author)-[:WROTE]->(b:Book)-[:REVIEWED_BY]->(r:Review)
RETURN a.name, b.title, r.text LIMIT 10
// Good: anchor limit before fan-out
MATCH (a:Author)-[:WROTE]->(b:Book)
WITH a, b LIMIT 10
MATCH (b)-[:REVIEWED_BY]->(r:Review)
RETURN a.name, b.title, r.text---
Cypher Runtime Selection
| Runtime | Select | Best For | Avoid When |
|---|---|---|---|
pipelined | CYPHER runtime=pipelined | Default OLTP; streaming, low memory | Unsupported operators fall back to slotted |
slotted | CYPHER runtime=slotted | Guaranteed stable behavior; debug | Performance-critical OLTP |
parallel | CYPHER 25 runtime=parallel | Large analytical scans; aggregations | OLTP, writes, short queries, Aura Free |
Pipelined is default for most queries. Parallel requires dbms.cypher.parallel.worker_limit configured; available on Enterprise and Aura Pro 2025+.
// Force parallel for large aggregation:
CYPHER 25 runtime=parallel
MATCH (n:Transaction) WHERE n.amount > 1000
RETURN n.currency, count(*), sum(n.amount)---
Query Monitoring Commands
// Live queries + resource usage:
SHOW QUERIES YIELD query, queryId, elapsedTimeMillis, allocatedBytes, status, username
// Running transactions:
SHOW TRANSACTIONS YIELD transactionId, currentQuery, currentQueryProgress, elapsedTime, status, username, cpuTime, activeLockCount // currentQueryProgress added [2026.03]
// Kill a specific transaction:
TERMINATE TRANSACTION $transactionId
// Kill a query:
TERMINATE QUERY $queryId
// Graph count stats (node/rel counts by label/type — feed into planner):
CALL db.stats.retrieve('GRAPH COUNTS') YIELD section, data RETURN section, data
// Token stats (label/property/rel-type IDs):
CALL db.stats.retrieve('TOKENS') YIELD section, data RETURN section, dataFull monitoring reference → references/stats-and-monitoring.md
---
Checklist
- [ ] Run
EXPLAINfirst — identifies plan problems without execution cost - [ ] Check for
AllNodesScan/NodeByLabelScan— missing index - [ ] Check for
CartesianProduct— missing join predicate - [ ] Check for
Eager— read/write conflict - [ ]
SHOW INDEXES— confirm relevant index exists andstate = 'ONLINE' - [ ] Create missing index; wait for ONLINE
- [ ] Run
PROFILEtwice — first warms cache, second is representative - [ ] Compare
dbHitsbefore/after fix - [ ] If
estimatedRowswildly off →CALL db.prepareForReplanning() - [ ] Push
LIMIT/WITH n LIMIT kbefore high-fanout operations - [ ] For CONTAINS/ENDS WITH — TEXT index, not RANGE
- [ ] For large analytical queries — consider
runtime=parallel - [ ] Kill long-running queries with
TERMINATE TRANSACTION
neo4j-query-tuning-skill
Diagnoses and fixes slow Neo4j Cypher queries by interpreting execution plans, identifying bad operators, and prescribing targeted fixes.
What it covers
- EXPLAIN vs PROFILE — when to use each; key metrics (dbHits, rows, estimatedRows, pageCacheHitRatio)
- Execution plan operators — complete reference table with good/bad signals and fix strategies
- Cardinality estimation — detecting stale stats, forcing replanning
- Common plan problems — missing indexes, CartesianProduct, Eager, over-traversal
- Planner hints —
USING INDEX,USING SCAN,USING JOIN ON - Runtime selection — slotted, pipelined, parallel; when each is appropriate
- Query monitoring —
SHOW QUERIES,SHOW TRANSACTIONS,TERMINATE TRANSACTION,db.stats.retrieve
Availability
Works with any Neo4j 2025.x / 2026.x instance (self-managed or Aura). Some features require Enterprise edition:
SHOW QUERIESfor other users' queries — Enterpriseruntime=parallel— Enterprise or Aura Pro 2025+
Install
# Using Claude Code (agentskills.io):
/skill install neo4j-query-tuning-skillReference Files
- `references/plan-operators.md` — complete operator table with all variants
- `references/stats-and-monitoring.md` — SHOW QUERIES, SHOW TRANSACTIONS, db.stats.*, index health, page cache
Cypher Execution Plan Operators — Full Reference
Read plans bottom-up: leaf operators at bottom, ProduceResults at top.
Lazy vs Eager: Most operators stream rows to parent as produced. Eager operators (marked ✗ below) must consume all input before emitting output — they materialise the full row set and can cause OOM on large inputs.
---
Leaf Operators (data source)
| Operator | Signal | Notes |
|---|---|---|
AllNodesScan | ✗✗ Bad | Scans entire node store. No label → no label index. Add label + property index. |
NodeByLabelScan | ✗ Bad | Scans all nodes of a label. No property index. Add RANGE index. |
NodeByIdSeek | ✓ | Lookup by internal node ID. Fast but fragile — IDs are not stable. Use elementId(). |
NodeIndexSeek | ✓ | Equality/range predicate satisfied via RANGE or LOOKUP index. Optimal. |
NodeUniqueIndexSeek | ✓ | Unique constraint index hit. Optimal. |
NodeIndexScan | ~ | Full scan of an index (no predicate selectivity). Faster than label scan; still linear. |
NodeIndexContainsScan | ✓ | TEXT index CONTAINS/STARTS WITH. Requires TEXT index on property. |
NodeIndexEndsWithScan | ✓ | TEXT index ENDS WITH. Requires TEXT index on property. |
RelationshipIndexSeek | ✓ | Relationship property index hit. |
RelationshipByIdSeek | ✓ | Lookup by relationship ID. |
DirectedRelationshipByIdSeek | ✓ | Directed rel by ID. |
UndirectedRelationshipByIdSeek | ~ | Undirected rel scan — matches twice (both directions). |
NodeByElementIdSeek | ✓ | Lookup by elementId() string. Preferred over id(). |
Argument | — | Passes outer scope variables into subquery. |
---
Traversal Operators
| Operator | Signal | Notes |
|---|---|---|
Expand(All) | ~ | Traverses all incoming/outgoing rels from a node. Normal. Limit fanout with WHERE/LIMIT. |
Expand(Into) | ~ | Finds rels between two already-matched nodes. Efficient for known endpoints. |
OptionalExpand(All) | ~ | OPTIONAL MATCH equivalent. Returns null row if no match. |
OptionalExpand(Into) | ~ | Optional expand between known endpoints. |
VarLengthExpand(All) | ✗ | Variable-length (a)-[*1..5]->(b) — can be expensive. Use QPE patterns or bound depth. |
VarLengthExpand(Pruning) | ~ | Pruned variable-length — avoids re-visiting nodes. Better than All. |
BFSPruningVarLengthExpand | ✓ | BFS-based; used for SHORTEST paths. Preferred. |
ShortestPath | ~ | Single shortest path. Replaced by QPE in Cypher 25. |
---
Join Operators
| Operator | Signal | Notes |
|---|---|---|
CartesianProduct | ✗ Bad | Two unconnected MATCH branches joined without predicate. O(m×n). Add WHERE join. |
NodeHashJoin | ~ | Hash join on node IDs. Eager — builds hash table. Memory-intensive for large inputs. |
ValueHashJoin | ~ | Hash join on arbitrary values (e.g. property equality). Eager. |
TriadicSelection | ✓ | Optimised "friend-of-friend excluding already-known" pattern. |
TriadicBuild / TriadicFilter | ✓ | Components of triadic optimisation. |
---
Filter / Projection Operators
| Operator | Signal | Notes |
|---|---|---|
Filter | ~ | Applies predicate after scan/expand. Non-index-backed predicate. Move to index if possible. |
CacheProperties | ✓ | Caches property values from store to avoid re-reads downstream. |
Projection | — | Evaluates expressions for output columns. |
DropResult | — | Discards results (e.g., write queries where RETURN is absent). |
ProduceResults | — | Root operator — emits final rows to client. |
---
Aggregation Operators
| Operator | Signal | Notes |
|---|---|---|
Aggregation | ✓ | Streaming aggregation; no full materialisation needed. |
EagerAggregation | ~ | Eager; must see all rows before emitting. Required for ORDER BY + aggregation. |
Distinct | ~ | Deduplication. Eager on large inputs. Use WITH DISTINCT to push earlier. |
OrderedAggregation | ✓ | Streaming aggregation when input is pre-sorted. |
OrderedDistinct | ✓ | Streaming dedup when input is pre-sorted. |
---
Sort / Limit Operators
| Operator | Signal | Notes |
|---|---|---|
Sort | ✗ | Eager full sort — O(n log n). Materialises all rows. Add LIMIT to convert to Top. |
Top | ✓ | Sort+Limit combined — O(n log k). Preferred; only keeps top k in memory. |
Top1 | ✓ | Single min/max — O(n). |
Limit | ✓ | Truncates rows non-eagerly. Push as early as possible in the plan. |
Skip | ~ | Offset pagination. Linear scan to skip position. Use keyset pagination for large offsets. |
PartialSort | ~ | Sort within already-grouped prefix. More efficient than full Sort. |
PartialTop | ✓ | Top within grouped prefix. |
---
Write Operators
| Operator | Notes |
|---|---|
Create | Creates nodes/rels. |
Merge | Merge with lock semantics. Requires constraint for atomicity. |
SetProperty / SetProperties | Sets properties. SetProperties batch-sets from map. |
SetLabels / RemoveLabels | Label mutation. |
Delete | Deletes node (fails if has rels). Use DetachDelete. |
DetachDelete | Deletes node + all its rels. |
DeleteRelationship | Deletes a relationship. |
---
Control / Subquery Operators
| Operator | Notes |
|---|---|
Eager | ✗✗ — Read/write conflict; materialises all upstream rows. Fix: add labels, collect-then-write, or CALL IN TRANSACTIONS. |
Apply | Correlated subquery execution (CALL (x) { }). One inner execution per outer row. |
SemiApply / AntiSemiApply | EXISTS { } / NOT EXISTS { } |
Optional | OPTIONAL MATCH — passes null row if no match. |
ConditionalApply | Subquery executed only if condition holds. |
AssertSameNode | Verifies MERGE did not create duplicate (unique constraint enforcement). |
TransactionForeach | CALL IN TRANSACTIONS outer loop. |
TransactionApply | CALL IN TRANSACTIONS inner execution. |
Union | Combines UNION branches. |
LoadCSV | LOAD CSV row reader. |
Foreach | FOREACH loop (write only, no RETURN). |
---
Reading the Plan: Worked Example
ProduceResults ← root; read last
|
Filter ← predicate not index-backed
|
Expand(All) ← traversal from matched node
|
NodeIndexSeek ← leaf; read first; index used ✓Index seek is efficient, expand is normal, filter applied after expand (not index-backed). If filter is selective, add a composite index or move the WHERE earlier.
---
Operator Hints
// Force index:
MATCH (p:Person {email: $email})
USING INDEX p:Person(email)
RETURN p
// Force label scan (ignore index):
MATCH (p:Person {active: true})
USING SCAN p:Person
RETURN p
// Force hash join at specific node:
MATCH (a:Author)-[:WROTE]->(b:Book)<-[:REVIEWED]-(r:Reviewer)
USING JOIN ON b
RETURN a.name, r.name
// Force index for relationship property:
MATCH ()-[t:TRANSFER {txId: $id}]->()
USING INDEX t:TRANSFER(txId)
RETURN tMultiple hints can be combined in one query.
Stats and Monitoring — Reference
SHOW QUERIES
Lists currently running queries across all databases (admin required for other users' queries).
SHOW QUERIES
YIELD query, queryId, database, username, elapsedTimeMillis, allocatedBytes,
status, activeLockCount, pageHits, pageFaults, protocol, connectionId
WHERE elapsedTimeMillis > 5000
RETURN queryId, username, elapsedTimeMillis, allocatedBytes, query
ORDER BY elapsedTimeMillis DESCKey fields:
queryId— use withTERMINATE QUERYelapsedTimeMillis— wall time since query startedallocatedBytes— heap allocated; high = memory pressurestatus—running,planning,waiting,closingactiveLockCount— >0 means write transaction holding lockspageHits/pageFaults— cache hit/miss counts for this query
Kill a single query:
TERMINATE QUERY "query-id-string"
YIELD queryId, username, message---
SHOW TRANSACTIONS
Lists all currently open transactions.
SHOW TRANSACTIONS
YIELD transactionId, database, username, currentQuery, elapsedTime,
status, cpuTime, waitTime, idleTime, activeLockCount,
pageHits, pageFaults, currentQueryId
WHERE status <> 'Terminated'
RETURN transactionId, username, status, elapsedTime, activeLockCount, currentQuery
ORDER BY elapsedTime DESCKey fields:
transactionId— use withTERMINATE TRANSACTIONstatus—Running,Blocked,Closing,TerminatedactiveLockCount— transactions blocking others will have high countscurrentQuery— the Cypher string currently executing (or last executed)elapsedTime— duration since transaction opened
Terminate a transaction:
TERMINATE TRANSACTION "neo4j-transaction-123"
YIELD transactionId, username, messageTerminate multiple:
TERMINATE TRANSACTIONS "tx-1", "tx-2"
YIELD transactionId, message---
Database Statistics
Graph Counts
Node/relationship counts by label and type — the data the planner uses for cardinality estimation.
CALL db.stats.retrieve('GRAPH COUNTS')
YIELD section, data
RETURN section, datadata map includes keys like:
nodes— total node countrelationships— total rel countnodesByLabel— map of{label: count}relsByType— map of{type: count}relsByTypeStartingLabel/relsByTypeEndingLabel— selectivity data
Token Stats
CALL db.stats.retrieve('TOKENS')
YIELD section, data
RETURN section, dataReturns internal token ID mappings for labels, property keys, and relationship types.
Retrieve All Stats
CALL db.stats.retrieveAllAnonymized('GRAPH COUNTS')
YIELD section, data
RETURN section, dataAnonymized version for sharing without exposing property names.
---
Statistics and Replanning
Config
dbms.cypher.statistics_divergence_threshold (default: 0.75)
Formula: abs(a - b) / max(a, b). At 0.75, plan invalidated when statistics change by 75% (~4× growth/shrink). Lower to replan more aggressively on growing databases.
Force Replanning
// Recalculate all statistics immediately (blocks until complete):
CALL db.prepareForReplanning()
// Resample a specific index asynchronously:
CALL db.resampleIndex("index-name")
// Resample all outdated indexes asynchronously:
CALL db.resampleOutdatedIndexes()Force replanning of a single query without changing stats:
CYPHER replan=force
MATCH (p:Person {email: $email}) RETURN p.nameSkip replanning (use cached plan even if stale — useful during high-load bursts):
CYPHER replan=skip
MATCH (p:Person {email: $email}) RETURN p.name---
Index Health Check
// Indexes not yet ONLINE (still populating or failed):
SHOW INDEXES YIELD name, type, labelsOrTypes, properties, state
WHERE state <> 'ONLINE'
RETURN name, type, labelsOrTypes, properties, state
// All online indexes:
SHOW INDEXES YIELD name, type, labelsOrTypes, properties, state, populationPercent
WHERE state = 'ONLINE'
RETURN name, type, labelsOrTypes, properties
ORDER BY type, labelsOrTypesIndex types and supported predicates:
| Type | = | <> | < > | IN | STARTS WITH | CONTAINS | ENDS WITH | POINT |
|---|---|---|---|---|---|---|---|---|
| RANGE | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| TEXT | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ |
| POINT | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
| FULLTEXT | — | — | — | — | — | ✓ | — | — |
| LOOKUP | node/rel by ID | — | — | — | — | — | — | — |
---
Query Log (server-side)
On self-managed Neo4j, slow queries log to neo4j.log and query.log:
Config options (neo4j.conf):
db.logs.query.enabled=INFO # Log all queries (verbose) or WARN (slow only)
db.logs.query.threshold=2s # Log queries taking longer than this
db.logs.query.parameter_logging_enabled=true
db.logs.query.allocation_logging_enabled=true
db.logs.query.page_logging_enabled=trueEach log entry includes: {elapsedMs} ms: {query} with optional params, allocated bytes, page hits/misses.
---
Page Cache Sizing
Small page cache → high pageFaults → disk I/O → slow queries.
// Current page cache stats:
CALL dbms.queryJmx("org.neo4j:instance=kernel#0,name=Page cache")
YIELD attributes
RETURN attributesOr from SHOW TRANSACTIONS/QUERIES:
pageHitshigh,pageFaultslow → cache is sufficientpageFaults> 1% of pageHits → increaseserver.memory.pagecache.sizeinneo4j.conf
Set page cache to hold the entire graph store (graph.db/ directory size).
Related skills
How it compares
Use for plan-driven graph tuning instead of generic SQL-style query tips that ignore Cypher operators.
FAQ
Who is neo4j-query-tuning-skill for?
Developers and small teams running Neo4j-backed APIs or agents who own query latency and cluster health themselves.
When should I use neo4j-query-tuning-skill?
Use it in Operate when dashboards spike dbHits or p95 latency; in Ship perf passes before launch; and whenever PROFILE shows CartesianProduct, Eager, or full scans on hot paths.
Is neo4j-query-tuning-skill safe to install?
Review the Security Audits panel on this Prism page and limit agent permissions to the Neo4j endpoints and credentials you intend to expose.