PROFILE Command

The PROFILE command executes a query and returns execution statistics. Use it to measure actual query performance and identify bottlenecks.

Overview

What PROFILE Does

  • Executes the query (unlike EXPLAIN)
  • Collects execution metrics
  • Returns performance statistics
  • Measures actual resource usage

When to Use PROFILE

  • Measuring query performance
  • Comparing query alternatives with real data
  • Identifying slow query sections
  • Validating optimization improvements
  • Benchmarking queries

Syntax

PROFILE <statement>

The PROFILE keyword prefixes any GQL statement:

PROFILE MATCH (n:Person) RETURN n.name;
PROFILE MATCH (a)-[:KNOWS]->(b) RETURN count(*);

Basic Examples

Simple Query Profile

PROFILE MATCH (p:Person) RETURN p.name;

Output:

| metric              | value |
|---------------------|-------|
| rows_returned       | 42    |
| columns             | 1     |
| execution_time_ms   | 5     |

Aggregation Query

PROFILE MATCH (p:Person)
RETURN count(p) AS person_count;

Output:

| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| columns             | 1     |
| execution_time_ms   | 3     |

Relationship Query

PROFILE MATCH (p:Person)-[:WORKS_FOR]->(c:Company)
WHERE c.industry = 'Technology'
RETURN p.name, c.name;

Output:

| metric              | value |
|---------------------|-------|
| rows_returned       | 156   |
| columns             | 2     |
| execution_time_ms   | 12    |

Metrics Reference

Current Metrics

MetricDescriptionType
rows_returnedNumber of result rowsInteger
columnsNumber of result columnsInteger
execution_time_msTotal execution time (milliseconds)Integer

Understanding Metrics

rows_returned

  • Total rows in the result set
  • For aggregations, typically 1 or few rows
  • High values may indicate missing LIMIT

columns

  • Number of columns in RETURN clause
  • Corresponds to query structure

execution_time_ms

  • Wall-clock time for query execution
  • Includes parsing, planning, and execution
  • Compare across query variations

Profile Analysis

Comparing Query Performance

Test different query formulations:

Query A: Property Filter

PROFILE MATCH (p:Person)
WHERE p.status = 'active'
RETURN count(p);
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| execution_time_ms   | 15    |

Query B: Pattern Filter

PROFILE MATCH (p:Person {status: 'active'})
RETURN count(p);
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| execution_time_ms   | 8     |

Analysis: Query B is faster with pattern-based filtering.

Before/After Optimization

Before Index:

PROFILE MATCH (p:Person)
WHERE p.email = 'alice@example.com'
RETURN p;
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| execution_time_ms   | 45    |

After Creating Index:

CREATE INDEX person_email ON Person(email);

PROFILE MATCH (p:Person)
WHERE p.email = 'alice@example.com'
RETURN p;
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| execution_time_ms   | 2     |

Analysis: Index reduced execution time by 95%.

Result Set Size Analysis

-- Large result set
PROFILE MATCH (n:Node) RETURN n;
| metric              | value   |
|---------------------|---------|
| rows_returned       | 100000  |
| execution_time_ms   | 250     |

Recommendation: Add LIMIT for interactive queries:

PROFILE MATCH (n:Node) RETURN n LIMIT 100;
| metric              | value |
|---------------------|-------|
| rows_returned       | 100   |
| execution_time_ms   | 8     |

Complex Query Examples

Aggregation with Grouping

PROFILE MATCH (p:Person)-[:LIVES_IN]->(c:City)
RETURN c.name, count(p) AS population
ORDER BY population DESC
LIMIT 10;
| metric              | value |
|---------------------|-------|
| rows_returned       | 10    |
| columns             | 2     |
| execution_time_ms   | 25    |

Variable-Length Path

PROFILE MATCH path = (a:Person {name: 'Alice'})-[:KNOWS*1..4]->(b:Person)
RETURN count(DISTINCT b) AS reachable;
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| columns             | 1     |
| execution_time_ms   | 85    |

Multi-Pattern Query

PROFILE MATCH (c:Customer)-[:PURCHASED]->(p:Product)
MATCH (p)-[:IN_CATEGORY]->(cat:Category)
WHERE cat.name = 'Electronics'
RETURN c.name, collect(p.name) AS products;
| metric              | value |
|---------------------|-------|
| rows_returned       | 245   |
| columns             | 2     |
| execution_time_ms   | 42    |

Performance Optimization Workflow

Step 1: Baseline Measurement

-- Measure current performance
PROFILE MATCH (o:Order)
WHERE o.date > date('2026-01-01')
RETURN o.id, o.total
ORDER BY o.total DESC;

Record baseline: execution_time_ms: 150

Step 2: Analyze Query

-- Check execution plan
EXPLAIN MATCH (o:Order)
WHERE o.date > date('2026-01-01')
RETURN o.id, o.total
ORDER BY o.total DESC;

Identify: No index on Order.date

Step 3: Apply Optimization

-- Create index
CREATE INDEX order_date ON Order(date);

Step 4: Measure Improvement

PROFILE MATCH (o:Order)
WHERE o.date > date('2026-01-01')
RETURN o.id, o.total
ORDER BY o.total DESC;

New result: execution_time_ms: 25

Improvement: 83% reduction in execution time.

Profiling Different Query Types

Data Modification

PROFILE CREATE (p:Person {name: 'New Person', age: 25});
| metric              | value |
|---------------------|-------|
| rows_returned       | 0     |
| columns             | 0     |
| execution_time_ms   | 3     |

Note: Modification queries execute fully; changes are committed.

Bulk Operations

PROFILE MATCH (p:Person)
WHERE p.status = 'pending'
SET p.status = 'active';
| metric              | value |
|---------------------|-------|
| rows_returned       | 0     |
| columns             | 0     |
| execution_time_ms   | 45    |

DELETE Operations

PROFILE MATCH (p:Person {temp: true})
DETACH DELETE p;
| metric              | value |
|---------------------|-------|
| rows_returned       | 0     |
| columns             | 0     |
| execution_time_ms   | 12    |

Warning: PROFILE executes the query. For destructive operations, use a test database.

Best Practices

Safe Profiling

DO:

  • Profile on test/staging environments first
  • Use LIMIT when profiling SELECT queries
  • Run multiple times for consistent measurements
  • Profile during representative load conditions

DON’T:

  • Profile destructive queries on production without caution
  • Compare profiles from different data sizes
  • Ignore cold-start effects (first run may be slower)

Consistent Measurements

-- Run multiple times
PROFILE MATCH (p:Person) RETURN count(p);  -- Run 1
PROFILE MATCH (p:Person) RETURN count(p);  -- Run 2
PROFILE MATCH (p:Person) RETURN count(p);  -- Run 3

-- Average the results for accuracy

Cache Warming

First run may include cache warming overhead:

-- Cold run (may be slower)
PROFILE MATCH (p:Person) RETURN count(p);

-- Warm run (cached data)
PROFILE MATCH (p:Person) RETURN count(p);

Compare warm runs for accurate benchmarks.

PROFILE vs EXPLAIN

AspectEXPLAINPROFILE
Executes queryNoYes
Shows planYesYes (with metrics)
Measures timeNoYes
Counts rowsNoYes
Data accessNoneFull
Side effectsNoneYes (for mutations)
Production safeYesUse caution

Workflow: EXPLAIN First, Then PROFILE

  1. EXPLAIN to understand the plan
  2. PROFILE to measure actual performance
  3. Optimize based on findings
  4. Repeat
-- Step 1: Understand plan
EXPLAIN MATCH (p:Person) WHERE p.age > 30 RETURN p;

-- Step 2: Measure performance
PROFILE MATCH (p:Person) WHERE p.age > 30 RETURN p;

-- Step 3: Optimize (e.g., create index)
CREATE INDEX person_age ON Person(age);

-- Step 4: Measure improvement
PROFILE MATCH (p:Person) WHERE p.age > 30 RETURN p;

Troubleshooting

Zero Rows Returned

PROFILE MATCH (p:NonExistent) RETURN p;
| metric              | value |
|---------------------|-------|
| rows_returned       | 0     |
| columns             | 1     |
| execution_time_ms   | 1     |

Causes:

  • No matching data
  • Incorrect label/property names
  • WHERE clause too restrictive

Diagnosis:

-- Check if label exists
MATCH (n) RETURN DISTINCT labels(n);

-- Simplify query
PROFILE MATCH (n) RETURN count(n);

Unexpectedly Slow Query

PROFILE MATCH (a)-[*]->(b) RETURN count(*);
| metric              | value |
|---------------------|-------|
| rows_returned       | 1     |
| execution_time_ms   | 15000 |

Causes:

  • Unbounded variable-length path
  • Large result set
  • Missing indexes

Solution: Add bounds:

PROFILE MATCH (a)-[*1..5]->(b) RETURN count(*);

Inconsistent Timing

If profiling shows different times on each run:

  1. Run query multiple times (warm cache)
  2. Profile during consistent load
  3. Use average of multiple runs
  4. Check for concurrent operations

Future Enhancements

Planned improvements to PROFILE:

Per-Operator Metrics

| operator              | rows  | time_ms | memory_kb |
|-----------------------|-------|---------|-----------|
| NodeScan              | 1000  | 5       | 120       |
| Filter                | 500   | 2       | 80        |
| ExpandRelationships   | 2000  | 15      | 300       |
| Project               | 2000  | 1       | 50        |
| TOTAL                 | 2000  | 23      | 550       |

Memory Profiling

| metric              | value    |
|---------------------|----------|
| peak_memory_kb      | 102400   |
| allocated_kb        | 85000    |
| cache_hits          | 1250     |
| cache_misses        | 45       |

I/O Statistics

| metric              | value |
|---------------------|-------|
| disk_reads          | 125   |
| disk_writes         | 0     |
| index_lookups       | 42    |

Last Updated: January 28, 2026 Geode Version: v0.1.3+ GQL Compliance: ISO/IEC 39075:2024