Query Execution Plans¶
Use the EXPLAIN command to understand how Quine executes your Cypher queries. This helps identify performance issues and optimize query patterns.
When to Use EXPLAIN
- Ad-hoc queries: Before running expensive queries on production data
- Ingest queries: When optimizing data loading performance
- Standing queries: To understand pattern matching behavior (though standing queries compile differently)
Using EXPLAIN¶
Prefix any Cypher query with EXPLAIN to see the execution plan without running the query:
EXPLAIN MATCH (n:Person)-[:KNOWS]->(m:Person)
WHERE n.age > 30
RETURN m.name
This returns a query plan showing how Quine will execute the query. The query is compiled but not executed, making it safe to use on production systems.
Query Plan Structure¶
The execution plan is returned as a JSON tree structure:
{
"operatorType": "Filter",
"args": {
"condition": "n.age > 30"
},
"identifiers": ["n", "m"],
"children": [
{
"operatorType": "Expand",
"args": {
"edgeName": "KNOWS",
"direction": "Outgoing"
},
"identifiers": ["n", "m"],
"children": [...]
}
],
"isReadOnly": true,
"isIdempotent": true,
"canContainAllNodeScan": true
}
Plans form a tree where each operator processes results from its children. Read the plan from the innermost children outward to understand execution order.
Root-Level Flags¶
The root of the query plan includes metadata flags:
| Flag | Description |
|---|---|
isReadOnly |
true if the query performs no writes to the graph |
isIdempotent |
true if running the query multiple times produces the same result |
canContainAllNodeScan |
true if the query may scan all nodes in the graph (performance warning) |
Performance Warning
If canContainAllNodeScan is true, your query may be slow on large graphs. Consider anchoring your query by node ID using WHERE id(n) = idFrom(...). See Using IDs in a Query for details.
Query Plan Operators¶
The following operators appear as operatorType in query plans:
Data Scanning & Entry Points¶
| Operator | Description |
|---|---|
AnchoredEntry |
Starts from a specific node ID or index lookup |
ArgumentEntry |
Starts from an externally provided node argument |
Graph Traversal¶
| Operator | Description |
|---|---|
Expand |
Follows edges with optional length bounds and direction constraints |
GetDegree |
Returns the count of edges matching specified constraints |
LocalNode |
Checks node labels and properties, optionally binding to a variable |
Data Flow & Combination¶
| Operator | Description |
|---|---|
Apply |
Executes one query then another sequentially (flatMap) |
Union |
Executes two queries and concatenates their results |
Or |
Executes the first query; uses second only if first returns nothing |
ValueHashJoin |
Joins two queries on matching property values |
SemiApply |
Filters results based on whether a sub-query succeeds |
Cross |
Calculates cross product of multiple query plan results |
Filtering & Transformation¶
| Operator | Description |
|---|---|
Filter |
Filters rows by a condition |
FilterMap |
Projects and optionally filters data from nested query results |
Optional |
Emits results, or input row if no results |
AdjustContext |
Adds, removes, or renames columns |
Unwind |
Expands a list into multiple rows |
Aggregation & Ordering¶
| Operator | Description |
|---|---|
EagerAggregation |
Groups and aggregates results |
Return |
Applies ORDER BY, DISTINCT, SKIP, LIMIT in one operator |
Skip |
Drops the first N results |
Limit |
Keeps only the first N results |
Sort |
Sorts results by expression(s) |
Distinct |
Removes duplicate rows |
Data Modification¶
| Operator | Description |
|---|---|
SetProperty |
Sets a single node property |
SetProperties |
Batch updates node properties |
SetLabels |
Adds or removes labels from a node |
SetEdge |
Creates or deletes an edge |
Delete |
Removes a node, relationship, or path |
Special Operations¶
| Operator | Description |
|---|---|
ProcedureCall |
Calls a user-defined procedure |
SubQuery |
Executes a sub-query and stitches results |
LocalProperty |
Generates a row containing a property value from a node |
LoadCSV |
Loads and iterates over CSV data |
Empty |
Returns no results |
Unit |
Returns input unchanged |
Reading an Execution Plan¶
Here's how to interpret a query plan for the query:
EXPLAIN MATCH (n:Person)-[:KNOWS]->(m) WHERE n.age > 30 RETURN m.name
- Start at the innermost operator - This is typically
AnchoredEntryor a scan operation - Follow the tree outward - Each parent operator processes its children's output
- Look for performance issues:
AnchoredEntrywithAllNodesScanindicates a full graph scan- Multiple
Crossoperators can indicate combinatorial explosion Filteroperators late in the plan may process many unnecessary rows
Query Type Considerations¶
Ad-hoc Queries¶
For interactive queries, use EXPLAIN before running expensive operations:
// Check the plan first
EXPLAIN MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m
// If plan looks reasonable, run the actual query
MATCH (n:Person)-[:KNOWS*1..5]->(m:Person)
WHERE n.name = "Alice"
RETURN m
Ingest Queries¶
For ingest queries that run repeatedly, optimize the plan once during development. Run the query as an ad-hoc query first to test behavior and performance before deploying it as part of your ingest pipeline:
// Test as ad-hoc query with sample data
MATCH (n) WHERE id(n) = idFrom("user", "test-user-123")
SET n.lastSeen = datetime()
RETURN n
// Then verify the plan anchors properly
EXPLAIN MATCH (n) WHERE id(n) = idFrom("user", $that.userId)
SET n.lastSeen = $that.timestamp
Look for AnchoredEntry rather than scans, since ingest queries execute for every record.
Standing Queries¶
Standing queries compile into a different internal representation optimized for incremental matching. While EXPLAIN shows the logical plan, the actual execution differs.
Before registering a standing query, run the pattern as an ad-hoc query to verify it matches expected data:
// Test the pattern as an ad-hoc query first
MATCH (order:Order)-[:PLACED_BY]->(customer:Customer)
WHERE order.total > 1000
RETURN order, customer
LIMIT 10
// Once satisfied, register as a standing query with the pattern
For debugging registered standing queries, use debugging procedures to inspect runtime state.