JK
>LOADING // LOADING
INITIALIZING VIEW
Go CLI tool that benchmarks four graph databases (CognoDB, Neo4j, Memgraph, TigerGraph) using the SNAP soc-Pokec social network dataset with 49,683 nodes and 100,000 relationships. Uses adapter pattern for database abstraction with 10 CLI entry points and interactive HTML chart generation.
Provides an apples-to-apples comparison of graph database performance under controlled conditions, eliminating variables like dataset size, query design, and client implementation. Targets academic researchers and engineers evaluating graph database options under constrained (free-tier) resources.
| Technology | Version | Purpose | Why Chosen |
|---|---|---|---|
| Go | 1.25.1 | Primary language | Strong concurrency primitives, fast compilation, good driver ecosystem |
| neo4j-go-driver/v6 | v6.2.0 | Bolt protocol client for CognoDB, Neo4j, Memgraph | Official driver, supports Bolt 5.x, parameterized queries |
| go-echarts/v2 | v2.7.2 | HTML chart generation | Go-native, generates standalone HTML |
| godotenv | v1.5.1 | Environment configuration | Standard .env loading |
| net/http | stdlib | REST client for TigerGraph | No external dependency needed |
| Method | Route | Purpose |
|---|---|---|
| Cypher | MATCH (n:Person {id: $id}) RETURN n.id AS id | Point lookup — single node by ID |
| Cypher | MATCH (n:Person {region: $val}) RETURN n.id AS id LIMIT 10 | Indexed lookup — nodes by region |
| Cypher | MATCH (n:Person {id: $id})-[:FRIEND]->(m:Person) RETURN collect(m.id) | 1-hop traversal — direct friends |
| Cypher | MATCH (n:Person {id: $id})-[:FRIEND]->()-[:FRIEND]->(m:Person) RETURN collect(DISTINCT m.id) | 2-hop traversal — friends of friends |
| Cypher | MATCH (n:Person {id: $id})-[:FRIEND]->()-[:FRIEND]->()-[:FRIEND]->(m:Person) RETURN collect(DISTINCT m.id) | 3-hop traversal — extended network |
| Cypher | MATCH (n:Person) RETURN n.region AS region, count(n) AS cnt ORDER BY cnt DESC LIMIT 10 | Aggregation — region distribution |
| Cypher | CREATE (n:Person {id: $id, region: $region, ...}) | Write — create new node |
| Column | Type | Notes |
|---|---|---|
| idPK | STRING | Unique constraint, indexed |
| region | STRING | Indexed |
| gender | STRING | Not indexed |
| age | STRING | Indexed (CognoDB, Neo4j, Memgraph only) |
| completion_percentage | STRING | Not indexed |
49,683 nodes extracted from SNAP soc-Pokec dataset. All properties are strings.
| Column | Type | Notes |
|---|---|---|
| source_id | STRING | Person.id (from) |
| target_id | STRING | Person.id (to) |
100,000 directed relationships. No properties on edges. Many-to-many cardinality.
Directed graph: Person --[FRIEND]--> Person. Many-to-many via graph relationships (no junction table). Relationships are first-class citizens in the graph model.
Decision: Use neo4j-go-driver/v6 for CognoDB, Neo4j, and Memgraph
Why: All three support Bolt protocol; Cypher is logically equivalent
Benefits: Code reuse, consistent behavior across databases, adapter pattern isolates DB-specific behavior
Trade-offs: Cannot test database-specific optimizations; Memgraph has its own Go driver
Decision: Single connection per benchmark run despite constant defining 10
Why: Measure single-connection latency; avoid connection pool effects
Benefits: Clean latency measurement without pool contention
Trade-offs: Doesn't test connection pooling behavior
Decision: One JSON object per line, append-only output
Why: Stream results without loading all into memory; easy to append across runs
Benefits: Resilient to crashes (partial results preserved)
Trade-offs: Requires full file scan for aggregation
Decision: Zero retries for benchmark queries to capture true latency
Why: Network blips and failures are part of real-world performance
Benefits: Honest performance measurement including failure modes
Trade-offs: Network blips cause false negatives
Decision: Each concurrent client creates its own database connection
Why: Simulates real-world multi-client scenarios; avoids shared connection contention
Benefits: Realistic concurrency testing with independent connections
Trade-offs: Higher resource usage; connection limits become the bottleneck
Decision: Use MERGE (upsert) instead of CREATE for node loading
Why: Makes loading idempotent — running multiple times doesn't create duplicates
Benefits: Safe re-runs, consistent dataset state
Trade-offs: MERGE is slightly slower than CREATE due to existence checks