Skip to content

LadybugDB — Northwind Benchmark Report ​

Run: 2026-10-09T09:33:13.262583-07:00 → 2026-10-09T09:33:26.118019-07:00Endpoint: ladybug://embedded//Users/tjsweet/src/NornicDB/bench-data/ladybug (database /Users/tjsweet/src/NornicDB/bench-data/ladybug)

Workload ​

  • Categories: 96 | Suppliers: 144 | Customers: 1,200
  • Products seeded: 48,000
  • Orders seeded: 48,000 (1..6 lines each)
  • Random seed: 42 (deterministic dataset)
  • Seed nodes: 97,440
  • Seed relationships: 312,050
  • Approx. seed payload (JSON-serialized): 35.4 MiB
  • Seed duration: 4,944.22 ms
  • Wipe duration: 0.00 ms
  • Index setup duration: 0.00 ms
  • Ingestion duration (row generation and writes): 0.00 ms
  • Ingestion nodes/sec: 0.00
  • Ingestion relationships/sec: 0.00
  • Seed batch size: 500 rows
  • Seed parallelism: 4 sessions per phase
  • Query workloads: 14
  • Iterations per query: 30
  • Warmup iterations per query: 5

Query Latency ​

QueryDescriptionSamplesMean (ms)Median (ms)P95 (ms)P99 (ms)Min (ms)Max (ms)StdDev (ms)Ops/sec
products_per_categoryProduct counts grouped by category, with a full result sort.300.880.871.001.050.711.060.071,112.59
customer_category_distinct_ordersFour-hop customer-to-category traversal with distinct-order aggregation.3058.2258.3659.6759.8255.5459.821.1017.17
optional_match_orders_countOptional product-to-order traversal with zero-match preservation and top-100 sorting.3010.2210.1510.6911.059.7811.180.2997.66
revenue_by_productRelationship-property arithmetic and revenue aggregation grouped by product.305.085.075.265.354.845.370.10196.46
products_by_supplierSupplier-to-product traversal with top-N aggregation and deterministic ties.300.770.760.810.830.710.830.031,292.86
orders_by_customerCustomer-to-order traversal grouped into a top-25 order-count ranking.300.980.981.031.050.911.050.041,016.09
revenue_by_categoryThree-hop category revenue aggregation from order-line quantities and product prices.3034.4534.4035.4135.5333.0735.570.6529.01
revenue_by_supplierSupplier-to-product-to-order traversal with revenue aggregation and top-25 sorting.3035.0935.0435.6936.0934.1836.230.4828.49
revenue_by_customerCustomer-order-product traversal with relationship-property revenue aggregation.3061.7561.0363.5468.3459.8970.282.0316.19
order_line_sales_by_countryOrder-line scan grouped by shipping country with line-count and unit aggregation.302.052.042.172.221.922.240.08485.05
low_stock_productsSelective numeric property filter followed by a stable top-100 product sort.302.332.322.572.632.132.650.13424.01
products_in_categorySelective category lookup and adjacent product traversal with a top-100 result.302.222.202.382.412.062.420.10445.89
order_line_quantity_distributionFull relationship-property scan grouped by line quantity.301.511.491.681.761.301.780.11660.23
customer_order_detailsSelective customer lookup followed by order-line expansion and computed row projection.302.632.592.793.042.463.150.14375.17
  • Overall mean latency: 15.58 ms
  • Measured query operations: 420
  • End-to-end query-loop throughput: 54.91 ops/sec
  • Query-latency-only aggregate throughput: 64.17 ops/sec
  • Query-loop duration: 7.649 s
  • Query-loop duration includes warmups and per-query setup; only measured iterations count toward the end-to-end rate.
  • Full lifecycle wall-clock (sampled): 13.037 s

Correctness ​

Seed counts (from the database's own count(...) queries):

EntityCount
Category96
Supplier144
Customer1,200
Product48,000
Order48,000
PART_OF edges48,000
SUPPLIES edges48,000
PURCHASED edges48,000
ORDERS edges168,050

Per-query result fingerprints (SHA-256 over canonicalised rows):

QueryRowsHashStable across iterations
products_per_category9691e9f1f063680a6d…Yes
customer_category_distinct_orders105da36214d5163220…Yes
optional_match_orders_count1008950fcdaab16eaeb…Yes
revenue_by_product1060b64c678f4c01fd…Yes
products_by_supplier25af1e9b5d1d663a02…Yes
orders_by_customer25ecff10cfcfa9cc34…Yes
revenue_by_category9623ba39858bf74ace…Yes
revenue_by_supplier2541900ee05a8f994a…Yes
revenue_by_customer25639a286559282c97…Yes
order_line_sales_by_country15a86030b2ba5eede9…Yes
low_stock_products100a8f2f994d920e6d8…Yes
products_in_category100e207262bf51ed857…Yes
order_line_quantity_distribution25e239e5f47878c862…Yes
customer_order_details1009d6ef178ee445057…Yes

No intra-run correctness errors.

Power Consumption ​

  • Samples collected: 12 (~1s each)
  • Sampled duration: 12.23 s
  • Avg CPU power: 12,963.9 mW
  • Avg GPU power: 40.0 mW
  • Avg package power: 13,003.9 mW
  • Estimated energy (benchmark window): 159.00 J

Memory Pressure ​

  • Samples collected: 15 (~1s each)
  • Avg used (active + wired + compressor): 24.3 GiB
  • Peak used: 24.8 GiB
  • Avg free: 5.1 GiB
  • Min free: 4.3 GiB
  • Avg compressed (logical): 7.1 GiB
  • Peak compressed: 7.1 GiB

Storage ​

  • Raw data files: 0 B (0 bytes)
  • Indexes/stats: 0 B (0 bytes)
  • Write-ahead logs: 0 B (0 bytes)
  • Metadata/bookkeeping: 0 B (0 bytes)
  • Preallocated scratch (excluded): 0 B (0 bytes)
  • Unclassified (other): 0 B (0 bytes)
  • Full data directory du: 25.1 MiB (26,279,936 bytes)
  • Classified sum: 0 B (0 bytes, Δ vs du = -26,279,936 bytes)

⚠️ Classifier/du mismatch: -26,279,936 bytes. A file type may be uncategorised — inspect the data directory manually and extend NORNIC_RULES / NEO4J_RULES.

Raw-data size is the comparison headline. Preallocated memtable/WAL scratch files (8 MiB memtable on Badger, 1 MiB GC discard log, etc.) are excluded because they hold the same bytes regardless of dataset size.

Queries ​

products_per_category ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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 ​

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

customer_order_details ​

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
Analytics privacy