Skip to content

Performance Comparison: Query Interfaces

CongraphDB provides three query interfaces with different performance characteristics. This guide helps you choose the right interface based on your performance requirements.

Executive Summary

Interface Best For Performance
Cypher Complex queries, analytics Optimized query execution
JavaScript API (CRUD) Simple operations Fast direct methods
Navigator Graph traversal Optimized traversal

Operation-Level Performance

CRUD Operations

Operation Cypher JavaScript API Winner
Create Node ~2ms ~0.5ms JavaScript API
Get Node by ID ~1.5ms ~0.3ms JavaScript API
Update Node ~2ms ~0.5ms JavaScript API
Delete Node ~2.5ms ~0.6ms JavaScript API
Create Edge ~2ms ~0.7ms JavaScript API
Get Edge ~1.5ms ~0.4ms JavaScript API

Recommendation: Use JavaScript API for all CRUD operations.

Single-Hop Traversal

Operation Cypher JavaScript API Navigator Winner
Find friends ~5ms ~8ms* ~3ms Navigator
Count friends ~4ms ~10ms* ~2ms Navigator
Filter friends ~5ms ~12ms* ~4ms Navigator

*Requires additional queries to resolve node IDs

Recommendation: Use Navigator for single-hop traversals.

Multi-Hop Traversal

Operation Cypher JavaScript API Navigator Winner
Friends of friends (2-hop) ~8ms ~25ms* ~6ms Navigator
3-hop traversal ~12ms ~40ms* ~9ms Navigator
4-hop traversal ~18ms ~60ms* ~14ms Navigator

*Requires chaining multiple find() calls

Recommendation: Use Navigator for multi-hop traversals (2-4 hops).

Complex Queries

Operation Cypher JavaScript API Navigator Winner
Aggregations (COUNT, SUM) ~6ms N/A N/A Cypher
Pattern comprehension ~8ms N/A N/A Cypher
Path finding (shortestPath) ~10ms N/A ~8ms Navigator
Multi-condition filtering ~7ms ~15ms* ~6ms Navigator

*Limited to simple pattern matching

Recommendation: - Aggregations → Cypher - Pattern comprehensions → Cypher - Path finding → Navigator - Multi-hop filtering → Navigator


Detailed Analysis

1. CRUD Performance

JavaScript API (Fastest)

// Direct method calls, no query parsing
const node = await api.getNode('node-id');  // ~0.3ms

Why it's fast: - Direct method calls - No query parsing overhead - Optimized internal lookups

Cypher (Slower for CRUD)

// Requires query parsing and execution
const result = await conn.query(`
  MATCH (n:User {id: 'node-id'}) RETURN n
`);  // ~1.5ms

Why it's slower: - Query parsing overhead - Query planning - Result transformation

2. Traversal Performance

// Fluent traversal with optimized execution
const fof = await api.nav(alice._id)
  .out('KNOWS')
  .out('KNOWS')
  .values();  // ~6ms for 2-hop

Why it's fast: - Pre-optimized traversal patterns - Direct CSR structure access - Minimal intermediate allocations

Cypher (Good for Complex Traversals)

// Query optimizer handles complex patterns
const result = await conn.query(`
  MATCH (a:User {id: 'alice'})-[:KNOWS]->()-[:KNOWS]->(fof:User)
  RETURN fof
`);  // ~8ms for 2-hop

Why it's good: - Query optimization - Index usage - Columnar storage benefits

// Requires multiple queries
const friends = await api.find({
  subject: alice._id,
  predicate: 'KNOWS',
  object: api.v('friend')
});  // ~8ms

const fof = [];
for (const f of friends) {
  const results = await api.find({
    subject: f.friend._id,
    predicate: 'KNOWS',
    object: api.v('fof')
  });
  fof.push(...results);
}  // ~25ms total

Why it's slow: - N+1 query pattern - Multiple round trips - No cross-query optimization

3. Aggregation Performance

Cypher (Best)

const result = await conn.query(`
  MATCH (u:User)-[:KNOWS]->(f:User)
  RETURN u.name, COUNT(f) AS friend_count
`);  // ~6ms

Why it's fast: - Optimized aggregation operators - Single-pass execution - Columnar storage benefits

JavaScript API (Not Supported)

// No native aggregation support
const friends = await api.find({...});
const count = friends.length;  // Manual, slow

Why it's slow: - Requires loading all results - Manual counting - No query optimization

4. Path Finding Performance

const path = await api.nav(alice._id)
  .out('KNOWS')
  .to(bob._id)
  .values();  // ~8ms

Why it's fast: - BFS algorithm implementation - Early termination when found - Optimized for graph storage

Cypher (Good for Complex Path Finding)

const result = await conn.query(`
  MATCH p = shortestPath(
    (alice:User {id: 'alice'})-[:KNOWS*]-(bob:User {id: 'bob'})
  )
  RETURN p
`);  // ~10ms

Why it's good: - Built-in shortestPath function - Supports variable-length paths - Handles complex constraints


Performance Optimization Tips

For CRUD Operations

  1. Use JavaScript API exclusively

    // Fast
    await api.createNode('User', {...});
    await api.updateNode(id, {...});
    await api.deleteNode(id, true);
    

  2. Batch operations when possible

    const nodes = await Promise.all([
      api.createNode('User', {...}),
      api.createNode('User', {...}),
      api.createNode('User', {...})
    ]);
    

  3. Use transactions for multiple writes

    await api.transaction(async (tx) => {
      await tx.createNode('User', {...});
      await tx.createNode('User', {...});
    });
    

For Traversals

  1. Use Navigator for multi-hop

    // Fast
    const fof = await api.nav(id)
      .out('KNOWS')
      .out('KNOWS')
      .values();
    

  2. Apply filters early

    // Faster (filter in database)
    const filtered = await api.nav(id)
      .out('KNOWS')
      .where('age > 25')
      .values();
    
    // Slower (filter in JavaScript)
    const all = await api.nav(id).out('KNOWS').values();
    const filtered = all.filter(f => f.age > 25);
    

  3. Use limit() to reduce result set

    const first10 = await api.nav(id)
      .out('KNOWS')
      .limit(10)
      .values();
    

For Complex Queries

  1. Use Cypher for aggregations

    const result = await conn.query(`
      MATCH (u:User)-[:KNOWS]->(f:User)
      RETURN u.name, COUNT(f) AS count
    `);
    

  2. Use pattern comprehensions

    const result = await conn.query(`
      MATCH (u:User {id: 'alice'})-[:KNOWS*1..2]->(f:User)
      RETURN f.name
    `);
    

  3. Leverage indexes

    // Create indexes on frequently queried properties
    await conn.query(`
      CREATE NODE TABLE User (
        id STRING,
        email STRING,
        age INT64,
        PRIMARY KEY (id)
      )
    `);
    // PRIMARY KEY is automatically indexed
    


Scalability Considerations

Memory Usage

Interface Memory Characteristics
Cypher Efficient for large result sets (columnar)
JavaScript API Low overhead for single operations
Navigator Minimal allocations for traversal

Database Size Impact

Operation Impact Recommendation
Schema definition Fixed overhead Define schema upfront
Indexes Increases size Add indexes selectively
WAL file Grows with writes Configure checkpointing

Concurrency

Operation Concurrent Support Recommendation
Reads Full concurrency Use multiple connections
Writes Serialized via WAL Batch writes when possible
Transactions Serializable isolation Keep transactions short

Performance Benchmarks

Test Environment

  • CPU: 4-core @ 3.0GHz
  • RAM: 16GB
  • Database Size: 100K nodes, 500K edges
  • Storage: SSD

Benchmark Results

Single-Hop Queries

Navigator.out().values():        3,120 ops/sec
Cypher MATCH with RETURN:         2,450 ops/sec
JavaScript API find():           1,850 ops/sec

Multi-Hop Queries (3-hop)

Navigator.out().out().out().values():   1,240 ops/sec
Cypher variable-length path:             980 ops/sec

CRUD Operations

JavaScript API createNode():     8,500 ops/sec
JavaScript API getNode():       12,300 ops/sec
JavaScript API updateNode():    7,800 ops/sec
Cypher CREATE:                  3,200 ops/sec
Cypher MATCH + RETURN:          5,600 ops/sec

Aggregations

Cypher COUNT():                 2,100 ops/sec
Cypher AVG():                   1,850 ops/sec
Cypher pattern comprehension:   1,650 ops/sec

Decision Matrix

Based on your query type:

Query Type Recommended Interface Reason
Create node JavaScript API 4x faster than Cypher
Get node by ID JavaScript API 5x faster than Cypher
Update node JavaScript API 4x faster than Cypher
Delete node JavaScript API 4x faster than Cypher
Single-hop traversal Navigator 1.5x faster than Cypher
Multi-hop (2-4 hops) Navigator 1.3x faster than Cypher
Multi-hop (5+ hops) Cypher Better optimization
Aggregations Cypher Only option
Pattern comprehensions Cypher Only option
Path finding Navigator Slightly faster
Complex filtering Navigator More efficient

Best Practices

1. Use the Right Tool for the Job

// CRUD: JavaScript API
const user = await api.createNode('User', {...});

// Traversal: Navigator
const friends = await api.nav(user._id).out('KNOWS').values();

// Analytics: Cypher
const result = await conn.query(`
  MATCH (u:User)-[:KNOWS]->(f:User)
  RETURN COUNT(f)
`);

2. Minimize Round Trips

// Bad: Multiple queries
const friends = await api.find({...});
for (const f of friends) {
  const friends2 = await api.find({...});  // N+1 problem
}

// Good: Single traversal
const friendsOfFriends = await api.nav(id)
  .out('KNOWS')
  .out('KNOWS')
  .values();

3. Use Transactions for Batch Operations

// Bad: Multiple separate operations
await api.createNode('User', {...});
await api.createNode('User', {...});
await api.createNode('User', {...});

// Good: Single transaction
await api.transaction(async (tx) => {
  await tx.createNode('User', {...});
  await tx.createNode('User', {...});
  await tx.createNode('User', {...});
});

4. Filter Early

// Bad: Filter in JavaScript
const all = await api.nav(id).out('KNOWS').values();
const filtered = all.filter(f => f.age > 25);

// Good: Filter in database
const filtered = await api.nav(id)
  .out('KNOWS')
  .where('age > 25')
  .values();

See Also