mcpbeat Sign in

Neo4j Query Tuning Skill

Diagnoses and fixes slow Neo4j Cypher queries by reading execution plans, identifying bad operators (AllNodesScan, CartesianProduct, Eager, NodeByLabelScan), and prescribing fixes (indexes, hints, query rewrites, runtime selection). Use when a query is slow, when EXPLAIN or PROFILE output needs interpretation, when dbHits or pageCacheHitRatio are poor, when cardinality estimation diverges from actuals, or when deciding between slotted/pipelined/parallel runtimes. Covers USING INDEX / USING SCAN / USING JOIN hints, db.stats.retrieve, SHOW QUERIES, SHOW TRANSACTIONS, TERMINATE TRANSACTION. Does NOT write new Cypher from scratch — use neo4j-cypher-skill. Does NOT cover GDS algorithm tuning — use neo4j-gds-skill. Does NOT cover index/constraint creation syntax details — use neo4j-cypher-skill references/indexes.md.

6k tokens
context cost
the whole folder, loaded on every use
4
files
instructions only
0
copies elsewhere
how many repositories repackaged it
101
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-query-tuning-skill

What comes with it

14 207 bytes besides the instruction
README.md
references/plan-operators.md
references/stats-and-monitoring.md

What it tells the agent to use

found in the instruction text
Bash runs shell commands — read the instruction before connecting
WebFetch fetches pages from the network

The instruction itself

21 sections, as written by the author

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 scratchneo4j-cypher-skill
  • GDS algorithm performanceneo4j-gds-skill
  • Schema design / data modellingneo4j-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.name

Query 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": "[email protected]"}}'

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.name

Wrong 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.title

CartesianProduct — 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 context

Eager — Read/Write Conflict

Three strategies (pick simplest):

  • Add specific labels to MATCH nodes so planner distinguishes read/write sets
  • Collect-then-write: WITH collect(n) AS nodes UNWIND nodes AS n SET n.x = 1
  • 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 ROWS

Expensive 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.name

Over-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, data

Full monitoring reference → references/stats-and-monitoring.md


Checklist

  • [ ] Run EXPLAIN first — 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 and state = 'ONLINE'
  • [ ] Create missing index; wait for ONLINE
  • [ ] Run PROFILE twice — first warms cache, second is representative
  • [ ] Compare dbHits before/after fix
  • [ ] If estimatedRows wildly off → CALL db.prepareForReplanning()
  • [ ] Push LIMIT / WITH n LIMIT k before 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

Other skills for the same job

different authors, same section of the catalogue
Clawdirect Dev
by ComeOnOliver
×1

Build agent-facing web experiences with ATXP-based authentication, following the ClawDirect pattern. Use this skill when building websites that AI agents interact with via MCP tools, implementing cookie-based agent auth, or creating agent skills for web apps. Provides templates using @longrun/turtle, Express, SQLite, and ATXP.

13k tokens
Mem Search
by thedotmack

Search claude-mem's persistent cross-session memory database. Use when user asks "did we already solve this?", "how did we do X last time?", or needs work from previous sessions.

1k tokens
Deepagents Thread Inspector
by langchain-ai
vendor

Inspect and explain conversations in the local Deep Agents Code SQLite session store. Use as a fallback when LangSmith trace tooling is unavailable, for offline or untraced sessions, or when asked to identify or summarize a local dcode thread, inspect checkpoint metadata, list recent local threads, or parse ~/.deepagents/.state/sessions.db and a thread UUID or prefix.

7k tokens scripts
Hive Terminal Tools Pty Sessions
by aden-hive

Use when you need state across calls — building env vars, navigating with cd, driving REPLs (python -i, mysql, psql, node), or responding to interactive prompts (sudo password, ssh host-key confirmation, mysql connection). Teaches the prompt-sentinel exec pattern (default mode), raw I/O for REPLs (raw_send=True then read_only=True), the one-in-flight-per-session rule, and the close-or-leak-against-the-cap discipline. Bash on macOS — never zsh; explicit shell=/bin/zsh is rejected. Read before calling terminal_pty_open.

1k tokens
Deepchat Data Import
by ThinkInAIXYZ

Help developers build third-party tools that import, inspect, migrate, or analyze DeepChat data. Use when Codex needs to work with DeepChat provider configuration, model configuration, MCP/app settings, sessions, messages, legacy chat data, `agent.db`, `chat.db`, SQLCipher encrypted SQLite, Electron safeStorage wrapped passwords, Tauri importers, or native macOS/Windows/Linux data access.

6k tokens
Agent Team
by anbeime

统一管理多智能体角色的团队协作框架,支持智能体动态组合、灵活协作和扩展新角色。智能体本质上是"角色定义",可以根据任务需求灵活组建团队,实现从会议决策到系统构建的完整能力。智能体角色明确分工:有干活的、有指挥的、有挑毛病的,能实时看到沟通过程,共享数据库记忆,确保上下文一致。

25k tokens zh
Recall
by parcadei

Query the memory system for relevant learnings from past sessions

313 tokens
Transcript Fixer
by daymade

>- Corrects speech-to-text transcription errors using dictionary rules and Claude's built-in AI (no external API key required — Native AI Correction is the DEFAULT). Stage 3 API is a backup for automation without Claude Code. Builds personalized correction databases that learn from each fix, auto-loads person-name ASR variants from your people roster, and reads per-domain context files that prime the AI pass for context-dependent homophones. Triggers when working with ASR/STT output containing recognition errors, homophones, garbled technical terms, person-name errors, or Chinese/English mixed content. Also triggers on requests to clean up meeting notes, lecture transcripts, interview recordings, or any text produced by speech recognition. Use this skill even when the user just says "fix this transcript", "clean up these meeting notes", or mentions garbled names without invoking ASR specifically.

297k tokens scripts

How to use it

Copy the folder

Take neo4j-contrib/neo4j-query-tuning-skill from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.