|
| 1 | +# sqlite-graph Performance Benchmarks |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the performance characteristics of sqlite-graph, including benchmark methodology, results, and hardware specifications. |
| 6 | + |
| 7 | +## Quick Results |
| 8 | + |
| 9 | +### Performance Goals - All Met ✅ |
| 10 | + |
| 11 | +| Goal | Target | Actual | Status | |
| 12 | +|------|--------|--------|--------| |
| 13 | +| Simple queries | <10ms | 2.18ms | ✅ PASS | |
| 14 | +| Graph traversal | <50ms | 2.68ms | ✅ PASS | |
| 15 | +| Node creation | <1ms | 286.79µs | ✅ PASS | |
| 16 | + |
| 17 | +### Top Performers |
| 18 | + |
| 19 | +| Operation | Ops/Second | Avg Time | |
| 20 | +|-----------|------------|----------| |
| 21 | +| Update node (multiple properties) | 38,353 | 26.07µs | |
| 22 | +| Query with limit | 38,107 | 26.24µs | |
| 23 | +| Find all paths (maxDepth 5) | 32,648 | 30.63µs | |
| 24 | +| Update node (single property) | 20,601 | 48.54µs | |
| 25 | +| Find shortest path (50 hops) | 12,494 | 80.04µs | |
| 26 | + |
| 27 | +### Category Averages |
| 28 | + |
| 29 | +| Category | Avg Time | Ops/Second | |
| 30 | +|----------|----------|------------| |
| 31 | +| Updates | 37.31µs | 29,477 | |
| 32 | +| Queries | 1.28ms | 10,382 | |
| 33 | +| Traversal | 647.48µs | 11,056 | |
| 34 | +| Creation | 441.08µs | 2,744 | |
| 35 | +| Transactions | 330.08µs | 3,030 | |
| 36 | +| Real-World | 801.72µs | 1,251 | |
| 37 | +| Deletes | 1.12ms | 1,121 | |
| 38 | + |
| 39 | +## Testing Methodology |
| 40 | + |
| 41 | +### Benchmark Structure |
| 42 | + |
| 43 | +Each benchmark follows this pattern: |
| 44 | + |
| 45 | +1. **Warmup Phase**: 2-5 iterations to warm up V8 JIT compiler and SQLite caches |
| 46 | +2. **Measurement Phase**: 10-100 iterations depending on operation cost |
| 47 | +3. **Statistics Collection**: Min, max, average times and operations/second |
| 48 | + |
| 49 | +```typescript |
| 50 | +benchmark(name: string, operation: () => void, iterations: number, warmup: number) |
| 51 | +``` |
| 52 | + |
| 53 | +### Data Generation |
| 54 | + |
| 55 | +- **Nodes**: Created with realistic properties (name, age, active status) |
| 56 | +- **Edges**: Created with typed relationships (KNOWS, REQUIRES, HAS_SKILL) |
| 57 | +- **Properties**: Mix of strings, numbers, and integers (SQLite uses integers for booleans) |
| 58 | +- **Dataset Sizes**: 100, 1,000, and 10,000 nodes for query benchmarks |
| 59 | + |
| 60 | +### Timing Precision |
| 61 | + |
| 62 | +- Uses Node.js `performance.now()` for microsecond precision |
| 63 | +- Results reported in microseconds (µs) and milliseconds (ms) |
| 64 | +- Operations per second calculated as: `1,000,000 / avgTimeInMicroseconds` |
| 65 | + |
| 66 | +### Database Configuration |
| 67 | + |
| 68 | +- **File-based**: `benchmarks/benchmark.db` (persistent storage) |
| 69 | +- **SQLite Mode**: Default settings (no special optimizations) |
| 70 | +- **Transaction Mode**: ACID-compliant with savepoint support |
| 71 | +- **Database Size**: 344 KB for benchmark test dataset |
| 72 | + |
| 73 | +## Hardware Specifications |
| 74 | + |
| 75 | +**Test Environment:** |
| 76 | +- **Platform**: macOS (Darwin 24.6.0) |
| 77 | +- **Node.js**: v22.x (or current LTS version) |
| 78 | +- **SQLite**: Version included with better-sqlite3 |
| 79 | +- **Storage**: SSD (file-based persistence) |
| 80 | + |
| 81 | +**Note**: Your results may vary based on: |
| 82 | +- CPU speed and architecture |
| 83 | +- Available RAM |
| 84 | +- Storage type (SSD vs HDD vs NVMe) |
| 85 | +- Operating system and I/O scheduler |
| 86 | +- Other running processes |
| 87 | + |
| 88 | +## Benchmark Categories |
| 89 | + |
| 90 | +### 1. Node Creation |
| 91 | + |
| 92 | +Tests the performance of creating nodes with various property configurations. |
| 93 | + |
| 94 | +```typescript |
| 95 | +// Simple node |
| 96 | +db.createNode('Person', { name: 'Alice' }); |
| 97 | + |
| 98 | +// Complex properties |
| 99 | +db.createNode('Person', { |
| 100 | + name: 'Alice', |
| 101 | + age: 30, |
| 102 | + |
| 103 | + active: 1 // SQLite uses integers for booleans |
| 104 | +}); |
| 105 | +``` |
| 106 | + |
| 107 | +**Results:** |
| 108 | +- Simple: 286.79µs (3,487 ops/sec) |
| 109 | +- Complex: 306.30µs (3,265 ops/sec) |
| 110 | + |
| 111 | +### 2. Edge Creation |
| 112 | + |
| 113 | +Tests relationship creation with and without properties. |
| 114 | + |
| 115 | +```typescript |
| 116 | +// Natural syntax: from RELATIONSHIP to |
| 117 | +db.createEdge(alice.id, 'KNOWS', bob.id); |
| 118 | +db.createEdge(job.id, 'REQUIRES', skill.id, { proficiency: 'expert' }); |
| 119 | +``` |
| 120 | + |
| 121 | +**Results:** |
| 122 | +- No properties: 339.17µs (2,948 ops/sec) |
| 123 | +- With properties: 348.16µs (2,872 ops/sec) |
| 124 | + |
| 125 | +### 3. Queries |
| 126 | + |
| 127 | +Tests various query patterns including filtering, sorting, and limiting. |
| 128 | + |
| 129 | +```typescript |
| 130 | +db.nodes('Person').exec(); // Full scan |
| 131 | +db.nodes('Person').where({ active: 1 }).exec(); // Filtered |
| 132 | +db.nodes('Person').orderBy('age', 'desc').exec(); // Sorted |
| 133 | +db.nodes('Person').limit(10).exec(); // Limited |
| 134 | +``` |
| 135 | + |
| 136 | +**Results (100 nodes):** |
| 137 | +- All nodes: 2.18ms (459 ops/sec) |
| 138 | +- Where clause: 389.75µs (2,566 ops/sec) |
| 139 | +- Order by: 2.51ms (398 ops/sec) |
| 140 | +- Limit: 26.24µs (38,107 ops/sec) |
| 141 | +- Complex: 358.93µs (2,786 ops/sec) |
| 142 | + |
| 143 | +### 4. Graph Traversal |
| 144 | + |
| 145 | +Tests BFS/DFS traversal at various depths and path-finding algorithms. |
| 146 | + |
| 147 | +```typescript |
| 148 | +db.traverseFrom(alice.id, 'KNOWS').exec(); // 1 hop |
| 149 | +db.traverseFrom(alice.id, 'KNOWS').maxDepth(5).exec(); // 5 hops |
| 150 | +db.shortestPath(alice.id, bob.id, 'KNOWS'); // Shortest path |
| 151 | +db.allPaths(alice.id, bob.id, 'KNOWS', 5); // All paths |
| 152 | +``` |
| 153 | + |
| 154 | +**Results:** |
| 155 | +- 1 hop: 2.68ms (373 ops/sec) |
| 156 | +- 5 hops: 158.93µs (6,292 ops/sec) |
| 157 | +- 10 hops: 287.88µs (3,474 ops/sec) |
| 158 | +- Shortest path: 80.04µs (12,494 ops/sec) |
| 159 | +- All paths: 30.63µs (32,648 ops/sec) |
| 160 | + |
| 161 | +### 5. Updates |
| 162 | + |
| 163 | +Tests single and multiple property updates. |
| 164 | + |
| 165 | +```typescript |
| 166 | +db.updateNode(alice.id, { age: 31 }); // Single property |
| 167 | +db.updateNode(alice.id, { age: 31, active: 1 }); // Multiple properties |
| 168 | +``` |
| 169 | + |
| 170 | +**Results:** |
| 171 | +- Single property: 48.54µs (20,601 ops/sec) |
| 172 | +- Multiple properties: 26.07µs (38,353 ops/sec) ⚡ **Fastest operation** |
| 173 | + |
| 174 | +### 6. Deletes |
| 175 | + |
| 176 | +Tests node deletion with and without cascading edge removal. |
| 177 | + |
| 178 | +```typescript |
| 179 | +db.deleteNode(alice.id); // Simple delete |
| 180 | +// Node with edges triggers CASCADE delete |
| 181 | +``` |
| 182 | + |
| 183 | +**Results:** |
| 184 | +- Simple: 613.24µs (1,631 ops/sec) |
| 185 | +- With edges: 1.63ms (612 ops/sec) |
| 186 | + |
| 187 | +### 7. Transactions |
| 188 | + |
| 189 | +Tests transactional operations including savepoint rollback. |
| 190 | + |
| 191 | +```typescript |
| 192 | +db.transaction(() => { |
| 193 | + // Create 10 or 100 nodes |
| 194 | + for (let i = 0; i < count; i++) { |
| 195 | + db.createNode('Person', { name: `Person ${i}` }); |
| 196 | + } |
| 197 | +}); |
| 198 | + |
| 199 | +// With savepoint rollback |
| 200 | +db.transaction((ctx) => { |
| 201 | + db.createNode('Person', { name: 'Test' }); |
| 202 | + ctx.savepoint('sp1'); |
| 203 | + db.createNode('Person', { name: 'Test2' }); |
| 204 | + ctx.rollbackTo('sp1'); // Only first node persists |
| 205 | + ctx.commit(); |
| 206 | +}); |
| 207 | +``` |
| 208 | + |
| 209 | +**Results:** |
| 210 | +- 10 nodes: 343.19µs (2,914 ops/sec) |
| 211 | +- 100 nodes: 1.02ms (978 ops/sec) |
| 212 | +- With savepoint: 330.08µs (3,030 ops/sec) |
| 213 | + |
| 214 | +### 8. Real-World Scenarios |
| 215 | + |
| 216 | +Tests realistic application patterns combining multiple operations. |
| 217 | + |
| 218 | +**Job Search Scenario:** |
| 219 | +```typescript |
| 220 | +// Create job, company, skills, requirements, and query matches |
| 221 | +``` |
| 222 | +- **Result**: 759.28µs (1,317 ops/sec) |
| 223 | + |
| 224 | +**Social Network Scenario:** |
| 225 | +```typescript |
| 226 | +// Create users, friendships, and query friends-of-friends |
| 227 | +``` |
| 228 | +- **Result**: 844.17µs (1,185 ops/sec) |
| 229 | + |
| 230 | +## Performance Characteristics |
| 231 | + |
| 232 | +### What's Fast |
| 233 | + |
| 234 | +- **Updates**: Extremely fast (38K ops/sec) - SQLite UPDATE operations are optimized |
| 235 | +- **Queries with LIMIT**: Very fast (38K ops/sec) - SQLite can skip scanning all rows |
| 236 | +- **Path finding**: Fast (12-32K ops/sec) - Efficient graph algorithms |
| 237 | +- **Simple operations**: Sub-millisecond for most single-node operations |
| 238 | + |
| 239 | +### What's Slower |
| 240 | + |
| 241 | +- **Full table scans**: Query all nodes without WHERE clause (459 ops/sec) |
| 242 | +- **Sorting**: ORDER BY requires full scan and sort (398 ops/sec) |
| 243 | +- **Cascading deletes**: Deleting nodes with many edges (612 ops/sec) |
| 244 | +- **Large transactions**: Linear scaling with operation count |
| 245 | + |
| 246 | +### Scaling Considerations |
| 247 | + |
| 248 | +1. **Node count**: Query performance degrades linearly with dataset size |
| 249 | +2. **Edge count**: Traversal performance depends on graph density |
| 250 | +3. **Transaction size**: Larger transactions are more efficient per-operation but have higher latency |
| 251 | +4. **Index usage**: WHERE clauses benefit from SQLite indexes on node properties |
| 252 | + |
| 253 | +## Running Benchmarks |
| 254 | + |
| 255 | +### Prerequisites |
| 256 | + |
| 257 | +```bash |
| 258 | +npm install |
| 259 | +npm run build |
| 260 | +``` |
| 261 | + |
| 262 | +### Run Full Suite |
| 263 | + |
| 264 | +```bash |
| 265 | +npx ts-node benchmarks/comprehensive-benchmark.ts |
| 266 | +``` |
| 267 | + |
| 268 | +### Output Format |
| 269 | + |
| 270 | +``` |
| 271 | +📝 Node Creation Benchmarks |
| 272 | +──────────────────────────────────────────────────────────────────────────────── |
| 273 | + Create single node |
| 274 | + Avg: 286.79µs | Ops/sec: 3487 |
| 275 | + Min: 228.83µs | Max: 501.96µs |
| 276 | +``` |
| 277 | + |
| 278 | +### Interpreting Results |
| 279 | + |
| 280 | +- **Avg**: Average time per operation (lower is better) |
| 281 | +- **Ops/sec**: Operations per second (higher is better) |
| 282 | +- **Min/Max**: Best and worst case times (shows variance) |
| 283 | +- **Variance**: High variance (max >> avg) suggests: |
| 284 | + - JIT compilation effects (first few iterations) |
| 285 | + - OS scheduling interference |
| 286 | + - SQLite cache effects |
| 287 | + |
| 288 | +## Comparison to Other Graph Databases |
| 289 | + |
| 290 | +### sqlite-graph vs Neo4j |
| 291 | + |
| 292 | +| Feature | sqlite-graph | Neo4j | |
| 293 | +|---------|--------------|-------| |
| 294 | +| Deployment | Single file, embedded | Server process | |
| 295 | +| Query syntax | TypeScript fluent API | Cypher query language | |
| 296 | +| Node creation | ~3,500 ops/sec | ~10,000 ops/sec (depends on config) | |
| 297 | +| Traversal | Microseconds for shallow | Optimized for deep traversals | |
| 298 | +| ACID | Yes (SQLite) | Yes (configurable) | |
| 299 | + |
| 300 | +### sqlite-graph vs Property Graphs in Memory |
| 301 | + |
| 302 | +| Feature | sqlite-graph | In-Memory (e.g., graphology) | |
| 303 | +|---------|--------------|------------------------------| |
| 304 | +| Persistence | File-based (automatic) | Manual serialization required | |
| 305 | +| Memory usage | Constant (disk-backed) | All data in RAM | |
| 306 | +| Query speed | Microseconds-milliseconds | Nanoseconds-microseconds | |
| 307 | +| Dataset size | Limited by disk | Limited by RAM | |
| 308 | + |
| 309 | +## Future Optimization Opportunities |
| 310 | + |
| 311 | +### Potential Improvements |
| 312 | + |
| 313 | +1. **Index optimization**: Add indexes on frequently queried properties |
| 314 | +2. **Prepared statements**: Cache compiled SQL for repeated queries |
| 315 | +3. **Batch operations**: Bulk insert/update APIs |
| 316 | +4. **Connection pooling**: For concurrent access patterns |
| 317 | +5. **WAL mode**: Write-Ahead Logging for better concurrent reads |
| 318 | +6. **Graph-specific indexes**: Adjacency list optimizations |
| 319 | + |
| 320 | +### Expected Impact |
| 321 | + |
| 322 | +- Indexes: 10-100x speedup for filtered queries |
| 323 | +- Prepared statements: 2-5x speedup for repeated queries |
| 324 | +- WAL mode: Better concurrent read performance |
| 325 | +- Bulk operations: Linear scaling improvement |
| 326 | + |
| 327 | +## Contributing Benchmarks |
| 328 | + |
| 329 | +To add new benchmarks: |
| 330 | + |
| 331 | +1. Add method to `BenchmarkSuite` class in [comprehensive-benchmark.ts](../benchmarks/comprehensive-benchmark.ts) |
| 332 | +2. Follow existing pattern: warmup + measurement + statistics |
| 333 | +3. Include realistic data and query patterns |
| 334 | +4. Update this documentation with results |
| 335 | + |
| 336 | +### Benchmark Checklist |
| 337 | + |
| 338 | +- [ ] Warmup iterations (2-5) |
| 339 | +- [ ] Sufficient measurement iterations (10-100) |
| 340 | +- [ ] Realistic data patterns |
| 341 | +- [ ] Clean database state between runs |
| 342 | +- [ ] Document expected performance characteristics |
| 343 | +- [ ] Update hardware specs if significantly different |
| 344 | + |
| 345 | +## References |
| 346 | + |
| 347 | +- [SQLite Performance Tuning](https://www.sqlite.org/speed.html) |
| 348 | +- [better-sqlite3 Documentation](https://github.com/WiseLibs/better-sqlite3) |
| 349 | +- Node.js [performance.now()](https://nodejs.org/api/perf_hooks.html#perf_hooksperformancenow) |
| 350 | + |
| 351 | +--- |
| 352 | + |
| 353 | +**Last Updated**: 2025-10-29 |
| 354 | +**Benchmark Version**: 1.0.0 |
| 355 | +**Test Dataset**: 344 KB (100-10,000 nodes) |
| 356 | +**Total Benchmarks**: 23 |
| 357 | +**Total Operations Tested**: 1,320 |
0 commit comments