Skip to content

NornicDB vs Neo4j — Northwind Benchmark Comparison ​

  • Products seeded: 48,000, Orders seeded: 48,000
  • Query workloads: 14 NornicDB / 14 Neo4j
  • Iterations/query: 30 NornicDB (5 warmup) / 30 Neo4j (5 warmup)
  • Seed batches / parallel sessions: 500 / 4 NornicDB; 500 / 4 Neo4j

Summary ​

MetricNornicDBNeo4jDeltaRatio
Overall mean latency (ms)0.1342.00-99.7%322.76×
End-to-end query-loop throughput (ops/sec)20.2219.57+3.3%1.03×
Query-latency-only aggregate throughput (ops/sec)7,684.1523.81+32175.6%322.76×
Query-loop duration (s)20.77321.458-3.2%1.03×
Seed duration (ms)5,388.935,390.93-0.0%1.00×
Wipe duration (ms)1.16100.14-98.8%86.70×
Index setup duration (ms)2.96492.42-99.4%166.41×
Ingestion duration (ms)5,384.814,798.36+12.2%0.89×
Ingestion nodes/sec18,095.3620,306.93-10.9%0.89×
Ingestion relationships/sec57,950.1065,032.61-10.9%0.89×
Avg CPU power (mW)9,245.255,859.92+57.8%0.63×
Avg GPU power (mW)205.311.23+16613.5%0.01×
Avg package power (mW)9,450.565,861.15+61.2%0.62×
Energy during benchmark (J)259.94257.77+0.8%0.99×
Benchmark wall-clock (s)28.4844.37-35.8%1.56×
Peak memory used (bytes)24.2 GiB23.5 GiB+2.7%0.97×
Raw data files (bytes)155,422,72053,207,040+192.1%0.34×

Delta = (NornicDB − Neo4j) / Neo4j. Ratio compares Neo4j to NornicDB for metrics where lower is better (latency, energy, disk), and NornicDB to Neo4j for throughput (higher is better).End-to-end query-loop throughput divides measured operations by the full suite window, including warmups and per-query setup; the query-latency-only rate excludes both.

NornicDB Parser Modes: default vs ANTLR ​

Query latency and throughput of the two NORNICDB_PARSER modes over the same seeded Northwind graph and the same workload. Each mode ran serialized from a freshly wiped data directory. Seeding, storage, power and memory are not compared here.

MetricNornicDB (default)NornicDB (ANTLR)DeltaANTLR slower by
Overall mean latency (ms)0.130.17+30.1%1.30×
End-to-end query-loop throughput (ops/sec)20.2221.08+4.3%0.96×
Query-latency-only aggregate throughput (ops/sec)7,684.155,908.17-23.1%1.30×
Query-loop duration (s)20.77319.923-4.1%0.96×

Delta = (ANTLR − default) / default. "ANTLR slower by" is ANTLR ÷ default for latency and default ÷ ANTLR for throughput, so values above 1.00× mean ANTLR mode is slower.

Per-query latency ​

QueryMean default (ms)Mean ANTLR (ms)Mean ×Median default (ms)Median ANTLR (ms)P95 default (ms)P95 ANTLR (ms)P99 default (ms)P99 ANTLR (ms)Ops/sec defaultOps/sec ANTLRSame result
products_per_category0.150.181.18×0.150.180.180.210.180.235,874.775,020.33Yes
customer_category_distinct_orders0.110.181.59×0.110.170.130.220.140.298,668.225,488.64Yes
optional_match_orders_count0.150.191.30×0.140.180.180.230.180.256,072.824,807.92Yes
revenue_by_product0.090.161.69×0.090.150.120.180.180.2010,394.266,224.71Yes
products_by_supplier0.110.141.28×0.110.140.120.160.140.178,585.946,700.67Yes
orders_by_customer0.120.131.10×0.120.130.140.150.150.157,890.937,268.54Yes
revenue_by_category0.140.201.44×0.140.200.160.240.170.256,100.714,367.55Yes
revenue_by_supplier0.120.161.32×0.120.160.150.190.160.207,782.445,985.98Yes
revenue_by_customer0.120.161.29×0.120.150.150.180.160.187,810.476,132.30Yes
order_line_sales_by_country0.110.141.27×0.100.140.170.170.180.188,599.686,835.79Yes
low_stock_products0.180.201.10×0.170.190.210.220.320.244,864.084,488.30Yes
products_in_category0.160.191.19×0.160.190.170.200.180.215,420.754,635.35Yes
order_line_quantity_distribution0.100.141.43×0.090.130.120.170.130.179,835.537,048.87Yes
customer_order_details0.160.211.30×0.160.210.170.220.190.235,211.804,188.51Yes

Mean × is ANTLR mean ÷ default mean. "Same result" compares each query's row count and SHA-256 result fingerprint across the two modes.

Both modes returned identical results for all 14 queries.

Full Query Suite ​

Each workload is reported independently with all recorded latency percentiles, range, sample count, and per-query rate.

products_per_category ​

Product counts grouped by category, with a full result sort.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.150.150.180.180.140.180.015,874.7796
Neo4j306.336.257.358.655.629.100.68157.4996
NornicDB (ANTLR)300.180.180.210.230.140.230.025,020.3396

Mean-latency ratio (Neo4j / NornicDB): 41.62×.

Mean-latency ratio (NornicDB ANTLR / default): 1.18×.

Cypher
cypher
MATCH (c:Category)<-[:PART_OF]-(p:Product)
			RETURN c.categoryName AS categoryName, count(p) AS productCount
			ORDER BY productCount DESC

customer_category_distinct_orders ​

Four-hop customer-to-category traversal with distinct-order aggregation.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.110.110.130.140.100.140.018,668.2210
Neo4j30172.48171.01182.50192.73165.32196.697.125.8010
NornicDB (ANTLR)300.180.170.220.290.150.320.035,488.6410

Mean-latency ratio (Neo4j / NornicDB): 1546.46×.

Mean-latency ratio (NornicDB ANTLR / default): 1.59×.

Cypher
cypher
MATCH (c:Customer)-[:PURCHASED]->(o:Order)-[:ORDERS]->(p:Product)-[:PART_OF]->(cat:Category)
			RETURN c.companyName AS companyName, cat.categoryName AS categoryName, count(DISTINCT o) AS orders
			ORDER BY orders DESC, companyName ASC, categoryName ASC
			LIMIT 10

optional_match_orders_count ​

Optional product-to-order traversal with zero-match preservation and top-100 sorting.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.150.140.180.180.130.180.016,072.82100
Neo4j3053.0552.9054.8855.7551.6656.090.9918.84100
NornicDB (ANTLR)300.190.180.230.250.160.260.034,807.92100

Mean-latency ratio (Neo4j / NornicDB): 364.67×.

Mean-latency ratio (NornicDB ANTLR / default): 1.30×.

Cypher
cypher
MATCH (p:Product)
			OPTIONAL MATCH (p)<-[r:ORDERS]-(o:Order)
			RETURN p.productName AS productName, count(o) AS orderCount
			ORDER BY orderCount DESC, productName ASC
			LIMIT 100

revenue_by_product ​

Relationship-property arithmetic and revenue aggregation grouped by product.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.090.090.120.180.070.200.0310,394.2610
Neo4j3067.6567.1070.4771.2865.3671.491.5114.7810
NornicDB (ANTLR)300.160.150.180.200.130.200.026,224.7110

Mean-latency ratio (Neo4j / NornicDB): 728.49×.

Mean-latency ratio (NornicDB ANTLR / default): 1.69×.

Cypher
cypher
MATCH (p:Product)<-[r:ORDERS]-(:Order)
			WITH p, sum(p.unitPrice * r.quantity) AS revenue
			RETURN p.productName AS productName, revenue
			ORDER BY revenue DESC, productName ASC
			LIMIT 10

products_by_supplier ​

Supplier-to-product traversal with top-N aggregation and deterministic ties.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.110.110.120.140.090.150.018,585.9425
Neo4j306.876.528.759.825.879.991.03145.3325
NornicDB (ANTLR)300.140.140.160.170.130.180.016,700.6725

Mean-latency ratio (Neo4j / NornicDB): 61.93×.

Mean-latency ratio (NornicDB ANTLR / default): 1.28×.

Cypher
cypher
MATCH (s:Supplier)-[:SUPPLIES]->(p:Product)
			RETURN s.companyName AS supplier, count(p) AS products
			ORDER BY products DESC, supplier ASC
			LIMIT 25

orders_by_customer ​

Customer-to-order traversal grouped into a top-25 order-count ranking.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.120.120.140.150.100.160.017,890.9325
Neo4j306.746.666.898.396.468.990.44148.1325
NornicDB (ANTLR)300.130.130.150.150.120.160.017,268.5425

Mean-latency ratio (Neo4j / NornicDB): 55.85×.

Mean-latency ratio (NornicDB ANTLR / default): 1.10×.

Cypher
cypher
MATCH (c:Customer)-[:PURCHASED]->(o:Order)
			RETURN c.companyName AS customer, count(o) AS orders
			ORDER BY orders DESC, customer ASC
			LIMIT 25

revenue_by_category ​

Three-hop category revenue aggregation from order-line quantities and product prices.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.140.140.160.170.120.170.016,100.7196
Neo4j3063.1862.3668.4669.2460.7469.482.3715.8296
NornicDB (ANTLR)300.200.200.240.250.180.250.024,367.5596

Mean-latency ratio (Neo4j / NornicDB): 444.62×.

Mean-latency ratio (NornicDB ANTLR / default): 1.44×.

Cypher
cypher
MATCH (c:Category)<-[:PART_OF]-(p:Product)<-[r:ORDERS]-(:Order)
			RETURN c.categoryName AS category, sum(p.unitPrice * r.quantity) AS revenue
			ORDER BY revenue DESC, category ASC

revenue_by_supplier ​

Supplier-to-product-to-order traversal with revenue aggregation and top-25 sorting.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.120.120.150.160.100.160.027,782.4425
Neo4j3060.9260.6862.6963.4259.3063.680.9616.4125
NornicDB (ANTLR)300.160.160.190.200.140.210.025,985.9825

Mean-latency ratio (Neo4j / NornicDB): 501.64×.

Mean-latency ratio (NornicDB ANTLR / default): 1.32×.

Cypher
cypher
MATCH (s:Supplier)-[:SUPPLIES]->(p:Product)<-[r:ORDERS]-(:Order)
			RETURN s.companyName AS supplier, sum(p.unitPrice * r.quantity) AS revenue
			ORDER BY revenue DESC, supplier ASC
			LIMIT 25

revenue_by_customer ​

Customer-order-product traversal with relationship-property revenue aggregation.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.120.120.150.160.090.160.027,810.4725
Neo4j3069.9769.6072.5973.8268.2174.291.4814.2925
NornicDB (ANTLR)300.160.150.180.180.140.180.016,132.3025

Mean-latency ratio (Neo4j / NornicDB): 573.54×.

Mean-latency ratio (NornicDB ANTLR / default): 1.29×.

Cypher
cypher
MATCH (c:Customer)-[:PURCHASED]->(:Order)-[r:ORDERS]->(p:Product)
			RETURN c.companyName AS customer, sum(p.unitPrice * r.quantity) AS revenue
			ORDER BY revenue DESC, customer ASC
			LIMIT 25

order_line_sales_by_country ​

Order-line scan grouped by shipping country with line-count and unit aggregation.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.110.100.170.180.090.190.038,599.6815
Neo4j3048.7948.5150.7851.6748.0551.690.8520.4915
NornicDB (ANTLR)300.140.140.170.180.120.190.026,835.7915

Mean-latency ratio (Neo4j / NornicDB): 437.32×.

Mean-latency ratio (NornicDB ANTLR / default): 1.27×.

Cypher
cypher
MATCH (o:Order)-[r:ORDERS]->(:Product)
			RETURN o.shipCountry AS country, count(r) AS orderLines, sum(r.quantity) AS units
			ORDER BY orderLines DESC, country ASC

low_stock_products ​

Selective numeric property filter followed by a stable top-100 product sort.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.180.170.210.320.150.360.044,864.08100
Neo4j307.577.477.798.977.329.450.37131.51100
NornicDB (ANTLR)300.200.190.220.240.180.240.014,488.30100

Mean-latency ratio (Neo4j / NornicDB): 42.49×.

Mean-latency ratio (NornicDB ANTLR / default): 1.10×.

Cypher
cypher
MATCH (p:Product)
			WHERE p.unitsInStock < 25
			RETURN p.productName AS product, p.unitsInStock AS unitsInStock, p.unitPrice AS unitPrice
			ORDER BY unitsInStock ASC, product ASC
			LIMIT 100

products_in_category ​

Selective category lookup and adjacent product traversal with a top-100 result.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.160.160.170.180.140.180.015,420.75100
Neo4j300.950.841.072.580.793.190.431,015.91100
NornicDB (ANTLR)300.190.190.200.210.170.210.014,635.35100

Mean-latency ratio (Neo4j / NornicDB): 6.06×.

Mean-latency ratio (NornicDB ANTLR / default): 1.19×.

Cypher
cypher
MATCH (c:Category {categoryID: 7})<-[:PART_OF]-(p:Product)
			RETURN p.productName AS product, p.unitPrice AS unitPrice, p.unitsInStock AS unitsInStock
			ORDER BY product ASC
			LIMIT 100

order_line_quantity_distribution ​

Full relationship-property scan grouped by line quantity.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.100.090.120.130.090.130.019,835.5325
Neo4j3022.5822.0525.9826.3621.5526.401.2944.2625
NornicDB (ANTLR)300.140.130.170.170.110.170.027,048.8725

Mean-latency ratio (Neo4j / NornicDB): 235.33×.

Mean-latency ratio (NornicDB ANTLR / default): 1.43×.

Cypher
cypher
MATCH ()-[r:ORDERS]->()
			RETURN r.quantity AS quantity, count(r) AS lineCount
			ORDER BY quantity ASC

customer_order_details ​

Selective customer lookup followed by order-line expansion and computed row projection.

EngineSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/secRows
NornicDB300.160.160.170.190.140.200.015,211.80100
Neo4j300.950.831.222.850.673.500.511,018.17100
NornicDB (ANTLR)300.210.210.220.230.190.230.014,188.51100

Mean-latency ratio (Neo4j / NornicDB): 5.94×.

Mean-latency ratio (NornicDB ANTLR / default): 1.30×.

Cypher
cypher
MATCH (c:Customer {customerID: 42})-[:PURCHASED]->(o:Order)-[r:ORDERS]->(p:Product)
			RETURN o.orderID AS orderID, p.productName AS product, r.quantity AS quantity,
			       p.unitPrice * r.quantity AS extendedPrice
			ORDER BY orderID ASC, product ASC
			LIMIT 100

Correctness ​

Seed verification. Post-seed counts reported by each database (via MATCH (n:Label) RETURN count(n) and equivalent edge queries).

EntityNornicDBNeo4jMatch
Category9696Yes
Supplier144144Yes
Customer1,2001,200Yes
Product48,00048,000Yes
Order48,00048,000Yes
PART_OF48,00048,000Yes
SUPPLIES48,00048,000Yes
PURCHASED48,00048,000Yes
ORDERS168,050168,050Yes

Per-query result fingerprints. Each engine runs the query on the first (warmup) iteration, canonicalises the full result set, and hashes it with SHA-256. A matching row_count + matching hash means the engines returned the same data.

QueryNornicDB rowsNeo4j rowsNornicDB hashNeo4j hashMatch
products_per_category969691e9f1f06368…91e9f1f06368…Yes
customer_category_distinct_orders10105da36214d516…5da36214d516…Yes
optional_match_orders_count1001008950fcdaab16…8950fcdaab16…Yes
revenue_by_product101060b64c678f4c…60b64c678f4c…Yes
products_by_supplier2525af1e9b5d1d66…af1e9b5d1d66…Yes
orders_by_customer2525ecff10cfcfa9…ecff10cfcfa9…Yes
revenue_by_category969623ba39858bf7…23ba39858bf7…Yes
revenue_by_supplier252541900ee05a8f…41900ee05a8f…Yes
revenue_by_customer2525639a28655928…639a28655928…Yes
order_line_sales_by_country1515a86030b2ba5e…a86030b2ba5e…Yes
low_stock_products100100a8f2f994d920…a8f2f994d920…Yes
products_in_category100100e207262bf51e…e207262bf51e…Yes
order_line_quantity_distribution2525e239e5f47878…e239e5f47878…Yes
customer_order_details1001009d6ef178ee44…9d6ef178ee44…Yes

Intra-run stability. Every iteration of each query re-fingerprints its result set; a mismatch within a single engine's run is flagged below.

  • No intra-run mismatches on either engine.

All correctness checks passed — both engines seeded identically and returned identical result sets (by row count and canonical SHA-256 fingerprint) for every benchmark query.

Storage ​

Raw data files only (preallocated scratch, WAL, and indexes excluded from the headline):

BucketNornicDBNeo4j
Raw data148.2 MiB (155,422,720 B)50.7 MiB (53,207,040 B)
Indexes / stats0 B (0 B)7.5 MiB (7,913,472 B)
Write-ahead logs260.0 KiB (266,240 B)144.4 MiB (151,400,448 B)
Metadata8.0 KiB (8,192 B)1.1 MiB (1,191,936 B)
Scratch (excluded)1.0 MiB (1,048,576 B)4.0 KiB (4,096 B)
Unclassified0 B (0 B)0 B (0 B)
Total du149.6 MiB203.8 MiB
  • Raw data ratio: 2.92× Neo4j (larger)
  • Full-dir ratio (includes scratch/WAL): 0.73× Neo4j

Power ​

NornicDBNeo4j
Samples2743
Duration (s)27.5143.98
CPU avg (mW)9,245.25,859.9
GPU avg (mW)205.31.2
Package avg (mW)9,450.65,861.1
Energy (J)259.94257.77

Memory Pressure ​

System-wide memory during each engine's full lifecycle (startup → benchmark → shutdown).

NornicDBNeo4j
Samples3147
Avg used (active+wired+compressor)23.4 GiB23.3 GiB
Peak used24.2 GiB23.5 GiB
Avg free7.3 GiB7.2 GiB
Min free6.4 GiB6.6 GiB
Avg compressed (logical)7.2 GiB7.2 GiB
Peak compressed7.2 GiB7.2 GiB

Notes ​

  • Power figures are Apple powermetrics estimates; treat as directional, not absolute. Apple's own docs note that reported averages are approximate.
  • Both databases were freshly initialized before each run; Neo4j was stopped during the NornicDB run, and vice versa, to isolate measurements.
  • Benchmarks ran over the Bolt protocol using the neo4j-go-driver.
  • Storage classification: NornicDB raw data = *.sst + *.vlog (LSM records + value log). Neo4j raw data = neostore*store.db* (record stores). Preallocated scratch files — Badger's 8 MiB memtable (*.mem) and 1 MiB discard log (DISCARD), and Neo4j empty *.id allocation files — are excluded because their size is fixed/preallocated and does not scale with the dataset.
Analytics privacy