- Kuzu700×
- Grafeofastest
- SurrealDBn/a
- Memgraph904×
- Neo4j1769×
- HelixDB1247×
In this benchmark run
LoraDB is fastest overall across 82 workloads
Geometric-mean slowdown per engine across every workload they share. LoraDB wins eight of twelve groups; Grafeo takes setup and writes; Kuzu takes strings and numerics. Numbers here come straight from comparisons/report.md — same suite, same fixtures, same seed.
Where LoraDB leads
Strongest across scans, predicates, traversals, and patterns.
LoraDB wins the group on every workload it touches in these areas. Strings and numerics belong to Kuzu (LoraDB is within ~22% on both); writes and setup belong to Grafeo (LoraDB pays roughly 2× on writes against Grafeo’s purpose-built crate path).
- scans6 workloads
- predicates12 workloads
- aggregates9 workloads
- pipeline9 workloads
- lists3 workloads
- sort3 workloads
- traversals15 workloads
- patterns4 workloads
Overall ranking
Total geometric-mean slowdown, ordered fastest to slowest.
Log scale. Bar length reflects log₁₀(slowdown) so multiples spanning 2.3×–57× remain visually comparable. Slowdowns are copied verbatim from comparisons/report.md.
Per group
Twelve groups, eighty-two workloads.
Each card shows the workload count, the group winner, and the per-engine slowdown chip. LoraDB sits in its own row so the comparison stays anchored even when LoraDB is not the row winner.
- Kuzu1.79×
- Grafeofastest
- SurrealDB28.3×
- Memgraph3.71×
- Neo4j7.75×
- HelixDB125×
- Kuzu6.37×
- Grafeo5.43×
- SurrealDB121×
- Memgraph27.5×
- Neo4j24.1×
- HelixDB287×
- Kuzu1.14×
- Grafeo1.83×
- SurrealDB21.3×
- Memgraph4.77×
- Neo4j3.78×
- HelixDB20.9×
- Kuzufastest
- Grafeo1.84×
- SurrealDB34.1×
- Memgraph11.3×
- Neo4j4.19×
- HelixDBn/a
- Kuzufastest
- Grafeo1.87×
- SurrealDB39.9×
- Memgraph10.7×
- Neo4j4.00×
- HelixDB20.4×
- Kuzu3.18×
- Grafeo1.89×
- SurrealDB44.7×
- Memgraph6.25×
- Neo4j6.94×
- HelixDB35.5×
- Kuzu1.27×
- Grafeo1.30×
- SurrealDB36.8×
- Memgraph4.97×
- Neo4j3.07×
- HelixDB17.2×
- Kuzu11.3×
- Grafeo3.21×
- SurrealDB32.3×
- Memgraph41.6×
- Neo4j42.2×
- HelixDB15.0×
- Kuzu1.41×
- Grafeo1.60×
- SurrealDB31.8×
- Memgraph4.28×
- Neo4j3.48×
- HelixDB16.4×
- Kuzu20.4×
- Grafeo5.88×
- SurrealDB110×
- Memgraph22.4×
- Neo4j17.8×
- HelixDB133×
- Kuzu2.80×
- Grafeo2.45×
- SurrealDB190×
- Memgraph7.59×
- Neo4j4.96×
- HelixDB22.2×
Summary table
The summary in one table.
Each engine column carries the geometric-mean slowdown of that engine vs the group winner across every workload they share. Empty cells are noted below.
| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| setup | 1 | 2.70× slower | 700× slower | fastest | omitted | 904× slower | 1769× slower | 1247× slower | Grafeo |
| writes | 9 | 0.61× slower | 1.79× slower | fastest | 28.3× slower | 3.71× slower | 7.75× slower | 125× slower | Grafeo |
| scans | 6 | fastest | 6.37× slower | 5.43× slower | 121× slower | 27.5× slower | 24.1× slower | 287× slower | LoraDB |
| predicates | 12 | fastest | 1.14× slower | 1.83× slower | 21.3× slower | 4.77× slower | 3.78× slower | 20.9× slower | LoraDB |
| strings | 5 | 1.22× slower | fastest | 1.84× slower | 34.1× slower | 11.3× slower | 4.19× slower | omitted | Kuzu |
| numerics | 6 | 1.08× slower | fastest | 1.87× slower | 39.9× slower | 10.7× slower | 4.00× slower | 20.4× slower | Kuzu |
| aggregates | 9 | fastest | 3.18× slower | 1.89× slower | 44.7× slower | 6.25× slower | 6.94× slower | 35.5× slower | LoraDB |
| pipeline | 9 | fastest | 1.27× slower | 1.30 × slower | 36.8× slower | 4.97× slower | 3.07× slower | 17.2× slower | LoraDB |
| lists | 3 | fastest | 11.3× slower | 3.21× slower | 32.3× slower | 41.6× slower | 42.2× slower | 15.0× slower | LoraDB |
| sort | 3 | fastest | 1.41× slower | 1.60× slower | 31.8× slower | 4.28× slower | 3.48× slower | 16.4× slower | LoraDB |
| traversals | 15 | fastest | 20.4× slower | 5.88× slower | 110× slower | 22.4× slower | 17.8× slower | 133× slower | LoraDB |
| patterns | 4 | fastest | 2.80× slower | 2.45× slower | 190× slower | 7.59× slower | 4.96× slower | 22.2× slower | LoraDB |
| total | 82 | fastest | 3.30× slower | 2.30× slower | 48.6× slower | 9.75× slower | 7.99× slower | 57.2× slower | LoraDB |
Per-workload detail
Every workload, every engine, raw timings.
Workload rows show the raw mean time plus the slowdown against the row’s winner. Omitted cells mean the workload has no like-for-like equivalent on that engine; the reason is captured under each group.
setup(1)
WinnerGrafeo| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| construct_empty | — | 4.06 µs2.70× slower | 1.05 ms700× slower | 1.50 µsfastest | omitted | 1.36 ms904× slower | 2.66 ms1769× slower | 1.87 ms1247× slower | Grafeo |
writes(9)
WinnerGrafeo| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| bulk_edges | 200 | 606.63 µsfastest | 2.01 ms3.31× slower | 19.86 ms32.7× slower | omitted | 3.18 ms5.24× slower | 2.65 ms4.37× slower | 309.68 ms510× slower | LoraDB |
| bulk_set_match | 1000 | 552.39 µs1.84× slower | 300.03 µsfastest | 315.88 µs1.05× slower | 6.15 ms20.5× slower | 726.92 µs2.42× slower | 916.11 µs3.05× slower | 3.57 ms11.9× slower | Kuzu |
| delete_node | 1000 | 355.70 µs1.47× slower | 598.35 µs2.48× slower | 241.43 µsfastest | 5.56 ms23.0× slower | 483.58 µs2.00× slower | 1.65 ms6.84× slower | 151.74 ms629× slower | Grafeo |
| merge_create | 1000 | 112.57 µs1.25× slower | 670.68 µs7.45× slower | 90.02 µsfastest | omitted | 677.96 µs7.53× slower | 2.48 ms27.6× slower | omitted | Grafeo |
| merge_existing | 1000 | 23.95 µs2.89× slower | 162.06 µs19.6× slower | 8.29 µsfastest | omitted | 304.94 µs36.8× slower | 647.72 µs78.1× slower | omitted | Grafeo |
| set_multiple_props | 1000 | 21.15 µsfastest | 169.72 µs8.03× slower | 182.77 µs8.64× slower | 5.54 ms262× slower | 313.89 µs14.8× slower | 571.61 µs27.0× slower | 2.37 ms112× slower | LoraDB |
| update_set | 1000 | 18.01 µsfastest | 188.62 µs10.5× slower | 208.61 µs11.6× slower | 6.04 ms335× slower | 315.49 µs17.5× slower | 732.47 µs40.7× slower | 2.91 ms162× slower | LoraDB |
| write_bulk | 1000 | 1.46 ms1.93× slower | 2.76 ms3.65× slower | 756.21 µsfastest | 26.66 ms35.3× slower | 3.13 ms4.14× slower | 6.34 ms8.38× slower | 771.22 ms1020× slower | Grafeo |
| write_single | 1000 | 14.90 µs2.20× slower | omitted | 6.78 µsfastest | 252.56 µs37.3× slower | 413.52 µs61.0× slower | 1.30 ms192× slower | 153.86 ms22704× slower | Grafeo |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
write_singleKuzu- Kuzu requires `CREATE NODE TABLE` before inserts; the empty fixture has no schema and adding one would change the iteration's measured cost.
merge_existingHelixDBSurrealDB- HelixDB has no MERGE/upsert; emulating it needs conditional var_as_if branching.
- SurrealDB's UPSERT semantics diverge from Cypher MERGE on which fields are matched vs set.
merge_createHelixDBSurrealDB- HelixDB has no MERGE/upsert (see merge_existing).
- See merge_existing — UPSERT semantics differ.
bulk_edgesSurrealDB- UNWIND-driven bulk RELATE requires scripted FOR loops; not a like-for-like comparison.
scans(6)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| distinct | 1000 | 198.38 µsfastest | 448.06 µs2.26× slower | 246.37 µs1.24× slower | omitted | 738.04 µs3.72× slower | 566.24 µs2.85× slower | omitted | LoraDB |
| lookup_by_id | 1000 | 716.64 nsfastest | 116.07 µs162× slower | 170.03 µs237× slower | 5.26 ms7340× slower | 305.39 µs426× slower | 601.36 µs839× slower | 2.30 ms3205× slower | LoraDB |
| lookup_by_id_indexed | 1000 | 684.00 nsfastest | 116.18 µs170× slower | 22.73 µs33.2× slower | 32.35 µs47.3× slower | 310.02 µs453× slower | 747.01 µs1092× slower | 2.62 ms3831× slower | LoraDB |
| range_filter | 1000 | 199.70 µs1.04× slower | 192.68 µsfastest | 214.22 µs1.11× slower | 9.29 ms48.2× slower | 1.22 ms6.31× slower | 589.16 µs3.06× slower | omitted | Kuzu |
| scan_filtered | 1000 | 149.08 µsfastest | 158.49 µs1.06× slower | 211.95 µs1.42× slower | 5.88 ms39.5× slower | 1.15 ms7.73× slower | 718.45 µs4.82× slower | 3.82 ms25.6× slower | LoraDB |
| scan_label | 1000 | 124.96 µsfastest | 131.34 µs1.05× slower | 213.71 µs1.71× slower | 5.01 ms40.1× slower | 1.61 ms12.9× slower | 661.84 µs5.30× slower | 2.70 ms21.6× slower | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
distinctHelixDBSurrealDB- HelixDB's dedup is node-level; there is no SELECT DISTINCT <property>.
- `value` is a reserved word in SurrealQL's SELECT VALUE clause; no clean equivalent.
predicates(12)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| where_compound_and_or | 1000 | 213.07 µsfastest | 230.83 µs1.08× slower | 646.50 µs3.03× slower | 10.23 ms48.0× slower | 997.22 µs4.68× slower | 584.96 µs2.75× slower | 3.23 ms15.2× slower | LoraDB |
| where_contains | 1000 | 145.04 µsfastest | 166.24 µs1.15× slower | 255.53 µs1.76× slower | 4.96 ms34.2× slower | 638.95 µs4.41× slower | 597.31 µs4.12× slower | 3.02 ms20.8× slower | LoraDB |
| where_ends_with | 1000 | 143.60 µsfastest | 167.41 µs1.17× slower | 236.22 µs1.64× slower | 5.06 ms35.2× slower | 726.66 µs5.06× slower | 581.26 µs4.05× slower | 2.99 ms20.8× slower | LoraDB |
| where_id_in_range | 1000 | 142.71 µs2.02× slower | 187.97 µs2.66× slower | 70.55 µsfastest | 7.54 ms107× slower | 433.95 µs6.15× slower | 663.12 µs9.40× slower | 3.13 ms44.3× slower | Grafeo |
| where_in_list | 1000 | 162.42 µsfastest | 196.06 µs1.21× slower | 270.30 µs1.66× slower | 5.59 ms34.4× slower | 606.63 µs3.74× slower | 627.53 µs3.86× slower | 2.92 ms18.0× slower | LoraDB |
| where_modulo_eq | 1000 | 127.32 µsfastest | 167.83 µs1.32× slower | 250.88 µs1.97× slower | 128.48 µs1.01× slower | 717.29 µs5.63× slower | 557.09 µs4.38× slower | 3.01 ms23.7× slower | LoraDB |
| where_not | 1000 | 166.05 µs1.01× slower | 164.07 µsfastest | 340.52 µs2.08× slower | 8.27 ms50.4× slower | 1.24 ms7.57× slower | 695.07 µs4.24× slower | 4.64 ms28.3× slower | Kuzu |
| where_or | 1000 | 147.16 µsfastest | 185.19 µs1.26× slower | 450.08 µs3.06× slower | 8.43 ms57.3× slower | 627.82 µs4.27× slower | 603.10 µs4.10× slower | 3.29 ms22.3× slower | LoraDB |
| where_starts_with | 1000 | 146.91 µsfastest | 168.08 µs1.14× slower | 249.22 µs1.70× slower | 5.12 ms34.9× slower | 727.06 µs4.95× slower | 596.02 µs4.06× slower | 3.29 ms22.4× slower | LoraDB |
| where_string_gte | 1000 | 180.97 µs1.07× slower | 168.40 µsfastest | 247.34 µs1.47× slower | 6.05 ms35.9× slower | 1.20 ms7.10× slower | 612.78 µs3.64× slower | 4.11 ms24.4× slower | Kuzu |
| where_subexpr | 1000 | 227.59 µs1.69× slower | 194.85 µs1.45× slower | 589.01 µs4.37× slower | 134.71 µsfastest | 1.76 ms13.1× slower | 584.16 µs4.34× slower | 4.73 ms35.1× slower | SurrealDB |
| where_two_props | 1000 | 152.69 µsfastest | 205.41 µs1.35× slower | 404.15 µs2.65× slower | 6.46 ms42.3× slower | 390.30 µs2.56× slower | 589.64 µs3.86× slower | 2.56 ms16.8× slower | LoraDB |
strings(5)
WinnerKuzu| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| string_concat | 1000 | 186.55 µs1.27× slower | 147.24 µsfastest | 283.23 µs1.92× slower | 5.86 ms39.8× slower | 1.68 ms11.4× slower | 577.43 µs3.92× slower | omitted | Kuzu |
| string_size | 1000 | 172.17 µs1.10× slower | 156.38 µsfastest | 251.12 µs1.61× slower | 5.23 ms33.5× slower | 1.70 ms10.9× slower | 567.80 µs3.63× slower | omitted | Kuzu |
| string_substring | 1000 | 210.20 µs1.21× slower | 173.57 µsfastest | 314.23 µs1.81× slower | 5.58 ms32.1× slower | 2.06 ms11.9× slower | 877.36 µs5.05× slower | omitted | Kuzu |
| string_to_lower | 1000 | 201.00 µs1.33× slower | 151.22 µsfastest | 297.66 µs1.97× slower | 5.03 ms33.3× slower | 1.72 ms11.4× slower | 684.21 µs4.52× slower | omitted | Kuzu |
| string_to_upper | 1000 | 185.41 µs1.22× slower | 151.96 µsfastest | 292.95 µs1.93× slower | 4.91 ms32.3× slower | 1.67 ms11.0× slower | 603.96 µs3.97× slower | omitted | Kuzu |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
string_to_upperHelixDB- HelixDB's DSL has no scalar string functions (upper/lower/substring/length/concat) — only property filters and graph traversal.
string_to_lowerHelixDB- No scalar string functions in the DSL (see string_to_upper).
string_substringHelixDB- No scalar string functions in the DSL (see string_to_upper).
string_sizeHelixDB- No scalar string functions in the DSL (see string_to_upper).
string_concatHelixDB- No scalar string functions in the DSL (see string_to_upper).
numerics(6)
WinnerKuzu| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| numeric_abs | 1000 | 174.85 µs1.13× slower | 154.99 µsfastest | 272.48 µs1.76× slower | 5.86 ms37.8× slower | 1.64 ms10.6× slower | 636.05 µs4.10× slower | omitted | Kuzu |
| numeric_ceil | 1000 | 171.69 µs1.10× slower | 155.40 µsfastest | 269.13 µs1.73× slower | 5.81 ms37.4× slower | 1.78 ms11.5× slower | 634.65 µs4.08× slower | omitted | Kuzu |
| numeric_floor | 1000 | 175.48 µs1.10× slower | 159.62 µsfastest | 326.82 µs2.05× slower | 5.99 ms37.5× slower | 1.64 ms10.3× slower | 609.30 µs3.82× slower | omitted | Kuzu |
| numeric_modulo | 1000 | 144.95 µs1.02× slower | 142.67 µsfastest | 236.39 µs1.66× slower | omitted | 1.59 ms11.2× slower | 637.61 µs4.47× slower | 2.97 ms20.8× slower | Kuzu |
| numeric_pow | 1000 | 166.22 µs1.16× slower | 143.34 µsfastest | 394.61 µs2.75× slower | 8.42 ms58.8× slower | 1.68 ms11.7× slower | 641.94 µs4.48× slower | 2.87 ms20.0× slower | Kuzu |
| numeric_round | 1000 | 180.68 µsfastest | 181.69 µs1.01× slower | 269.75 µs1.49× slower | 5.87 ms32.5× slower | 1.68 ms9.30× slower | 581.78 µs3.22× slower | omitted | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
numeric_absHelixDB- HelixDB's Expr supports +,-,*,/,% but no abs/floor/ceil/round.
numeric_moduloSurrealDB- SurrealQL parser rejects bare `%` inside SELECT projections (same parse limit as grouped_aggregation).
numeric_floorHelixDB- HelixDB's Expr supports +,-,*,/,% but no abs/floor/ceil/round.
numeric_ceilHelixDB- HelixDB's Expr supports +,-,*,/,% but no abs/floor/ceil/round.
numeric_roundHelixDB- HelixDB's Expr supports +,-,*,/,% but no abs/floor/ceil/round.
aggregates(9)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| aggregate_avg | 1000 | 81.43 µsfastest | 253.54 µs3.11× slower | 202.26 µs2.48× slower | 6.22 ms76.4× slower | 562.93 µs6.91× slower | 606.76 µs7.45× slower | 3.20 ms39.3× slower | LoraDB |
| aggregate_collect | 1000 | 78.90 µsfastest | 275.13 µs3.49× slower | 214.58 µs2.72× slower | 6.79 ms86.0× slower | 599.26 µs7.60× slower | 619.24 µs7.85× slower | omitted | LoraDB |
| aggregate_count | 1000 | 59.50 µs2.77× slower | 250.00 µs11.7× slower | 21.44 µsfastest | 209.05 µs9.75× slower | 381.02 µs17.8× slower | 588.34 µs27.4× slower | omitted | Grafeo |
| aggregate_count_distinct | 1000 | 104.05 µsfastest | 441.70 µs4.24× slower | 213.10 µs2.05× slower | omitted | 540.27 µs5.19× slower | 635.68 µs6.11× slower | omitted | LoraDB |
| aggregate_max | 1000 | 79.97 µsfastest | 263.33 µs3.29× slower | 200.95 µs2.51× slower | 6.08 ms76.0× slower | 572.73 µs7.16× slower | 619.56 µs7.75× slower | 2.86 ms35.7× slower | LoraDB |
| aggregate_min | 1000 | 79.67 µsfastest | 258.01 µs3.24× slower | 197.17 µs2.47× slower | 5.95 ms74.7× slower | 552.13 µs6.93× slower | 563.60 µs7.07× slower | 2.49 ms31.3× slower | LoraDB |
| aggregate_sum | 1000 | 78.09 µsfastest | 263.06 µs3.37× slower | 199.65 µs2.56× slower | 6.23 ms79.8× slower | 524.56 µs6.72× slower | 568.81 µs7.28× slower | 2.82 ms36.1× slower | LoraDB |
| grouped_aggregation | 1000 | 154.46 µsfastest | 549.19 µs3.56× slower | 269.45 µs1.74× slower | omitted | 751.51 µs4.87× slower | 969.01 µs6.27× slower | omitted | LoraDB |
| top_k | 1000 | 186.50 µsfastest | 248.58 µs1.33× slower | 424.61 µs2.28× slower | 6.40 ms34.3× slower | 955.44 µs5.12× slower | 792.15 µs4.25× slower | omitted | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
aggregate_countHelixDB- The HelixDB enterprise-dev image rejects `count()` dynamic queries with `rate limit exceeded` (its other 9 workloads run fine). The count handler is wired in helixdb.rs and would run on a server without that limit.
aggregate_collectHelixDB- aggregate_by offers Count/Sum/Min/Max/Mean, no list collect.
aggregate_count_distinctHelixDBSurrealDB- Needs count(DISTINCT); count() is rate-limited on the enterprise-dev image and value-level DISTINCT isn't exposed.
- count(DISTINCT) has no direct SurrealQL aggregate; requires nested SELECT + array::distinct.
grouped_aggregationHelixDBSurrealDB- group_count groups by a stored property, not a computed value % 10 key.
- SurrealQL rejects `%` inside a SELECT projection that's then used as a GROUP BY key (parse error).
pipeline(9)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| case_when | 1000 | 173.33 µsfastest | 186.25 µs1.07× slower | 256.32 µs1.48× slower | 7.38 ms42.6× slower | 1.86 ms10.8× slower | 796.60 µs4.60× slower | 2.78 ms16.1× slower | LoraDB |
| coalesce_existing | 1000 | 161.92 µsfastest | 162.41 µs1.00× slower | 260.13 µs1.61× slower | 7.51 ms46.4× slower | 1.62 ms9.99× slower | 628.36 µs3.88× slower | 3.32 ms20.5× slower | LoraDB |
| computed_in_return | 1000 | 151.15 µs1.07× slower | 140.92 µsfastest | 241.22 µs1.71× slower | 7.35 ms52.1× slower | 1.61 ms11.5× slower | 639.09 µs4.54× slower | 2.99 ms21.2× slower | Kuzu |
| distinct_with_order | 1000 | 508.01 µs2.08× slower | 549.89 µs2.25× slower | 243.88 µsfastest | omitted | 692.42 µs2.84× slower | 617.76 µs2.53× slower | omitted | Grafeo |
| predicate_via_function | 1000 | 238.39 µs1.38× slower | 172.38 µsfastest | 434.70 µs2.52× slower | 7.45 ms43.2× slower | 1.77 ms10.3× slower | 544.38 µs3.16× slower | omitted | Kuzu |
| with_aggregate_then_filter | 1000 | 148.70 µsfastest | 475.77 µs3.20× slower | 267.49 µs1.80× slower | omitted | 567.44 µs3.82× slower | 710.00 µs4.77× slower | omitted | LoraDB |
| with_distinct_then_count | 1000 | 202.72 µsfastest | 540.85 µs2.67× slower | 244.75 µs1.21× slower | omitted | 597.19 µs2.95× slower | 812.09 µs4.01× slower | omitted | LoraDB |
| with_pipeline | 1000 | 186.41 µsfastest | 333.60 µs1.79× slower | 218.95 µs1.17× slower | 5.98 ms32.1× slower | 678.15 µs3.64× slower | 581.26 µs3.12× slower | omitted | LoraDB |
| with_two_chained | 1000 | 313.64 µs1.41× slower | 222.76 µsfastest | 388.95 µs1.75× slower | 8.12 ms36.5× slower | 1.22 ms5.46× slower | 608.48 µs2.73× slower | 4.25 ms19.1× slower | Kuzu |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
with_pipelineHelixDB- Returns count(...); count() is rate-limited on the enterprise-dev image (see aggregate_count).
with_distinct_then_countHelixDBSurrealDB- count() rate-limited and no value-level DISTINCT.
- DISTINCT on `value` needs SELECT VALUE, where `value` is reserved (same parse limit as the `distinct` workload).
with_aggregate_then_filterHelixDBSurrealDB- Group-then-having on a computed key (value % 10) isn't expressible; group_count keys on a stored property.
- Groups on `value % 10`; SurrealQL rejects `%` in a projection used as a GROUP BY key (same parse limit as grouped_aggregation).
predicate_via_functionHelixDB- WHERE size(name) needs a length() scalar the DSL doesn't provide.
distinct_with_orderHelixDBSurrealDB- No value-level DISTINCT (see distinct).
- DISTINCT + ORDER BY on `value`, which is reserved in both SELECT VALUE and ORDER BY (see distinct, order_by_multi_key).
lists(3)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| list_in_construction | 1000 | 177.94 µs1.05× slower | 168.89 µsfastest | 450.42 µs2.67× slower | 6.14 ms36.4× slower | 2.05 ms12.1× slower | 733.38 µs4.34× slower | 2.67 ms15.8× slower | Kuzu |
| list_unwind_explicit | 1000 | 1.11 µsfastest | 165.87 µs149× slower | 7.11 µs6.40× slower | 33.52 µs30.2× slower | 322.60 µs291× slower | 689.53 µs621× slower | omitted | LoraDB |
| range_function | 1000 | 18.86 µsfastest | 193.49 µs10.3× slower | 38.62 µs2.05× slower | omitted | 404.85 µs21.5× slower | 555.15 µs29.4× slower | omitted | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
range_functionSurrealDB- SurrealQL has no row-generating numeric range (no UNWIND/range equivalent); the explicit-list unwind is covered by list_unwind_explicit.
sort(3)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| order_by_id_asc | 1000 | 167.95 µsfastest | 234.56 µs1.40× slower | 226.74 µs1.35× slower | 5.24 ms31.2× slower | 738.63 µs4.40× slower | 718.84 µs4.28× slower | 3.20 ms19.1× slower | LoraDB |
| order_by_multi_key | 1000 | 211.99 µsfastest | 276.69 µs1.31× slower | 442.71 µs2.09× slower | omitted | 871.82 µs4.11× slower | 554.84 µs2.62× slower | 2.80 ms13.2× slower | LoraDB |
| skip_limit | 1000 | 162.19 µsfastest | 251.28 µs1.55× slower | 233.92 µs1.44× slower | 5.24 ms32.3× slower | 704.66 µs4.34× slower | 612.55 µs3.78× slower | 2.87 ms17.7× slower | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
order_by_multi_keySurrealDB- `value` is reserved in SurrealQL ORDER BY clauses (parse error).
traversals(15)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| direct_record_traversal | 500 | 831.70 nsfastest | 255.86 µs308× slower | 64.09 µs77.1× slower | 62.98 µs75.7× slower | 384.41 µs462× slower | 616.93 µs742× slower | 1.55 ms1861× slower | LoraDB |
| recursive_depth2 | 500 | 968.69 nsfastest | 499.89 µs516× slower | 62.29 µs64.3× slower | 110.22 µs114× slower | 380.49 µs393× slower | 601.14 µs621× slower | 1.69 ms1742× slower | LoraDB |
| recursive_depth3 | 500 | 1.04 µsfastest | 540.33 µs520× slower | 62.87 µs60.5× slower | 147.78 µs142× slower | 382.45 µs368× slower | 592.76 µs570× slower | 1.46 ms1402× slower | LoraDB |
| recursive_depth5 | 500 | 1.16 µsfastest | 511.05 µs440× slower | 62.30 µs53.7× slower | 225.94 µs195× slower | 426.52 µs367× slower | 606.31 µs522× slower | 1.65 ms1425× slower | LoraDB |
| relation_filter | 500 | 117.78 µsfastest | 406.36 µs3.45× slower | omitted | 18.70 ms159× slower | 868.53 µs7.37× slower | 586.82 µs4.98× slower | 2.50 ms21.2× slower | LoraDB |
| traversal_count_one_hop | 500 | 59.36 µsfastest | 288.00 µs4.85× slower | 141.39 µs2.38× slower | 106.91 µs1.80× slower | 394.49 µs6.65× slower | 609.51 µs10.3× slower | omitted | LoraDB |
| traversal_filter_one_hop | 500 | 138.99 µsfastest | 405.25 µs2.92× slower | 259.07 µs1.86× slower | 21.51 ms155× slower | 1.04 ms7.49× slower | 582.58 µs4.19× slower | 3.15 ms22.7× slower | LoraDB |
| traversal_one_hop | 500 | 125.98 µsfastest | 360.18 µs2.86× slower | 263.65 µs2.09× slower | 25.78 ms205× slower | 1.14 ms9.06× slower | 570.56 µs4.53× slower | 2.59 ms20.5× slower | LoraDB |
| traversal_reverse | 500 | 123.67 µsfastest | 365.12 µs2.95× slower | 266.69 µs2.16× slower | 24.71 ms200× slower | 1.10 ms8.93× slower | 591.25 µs4.78× slower | 2.62 ms21.2× slower | LoraDB |
| traversal_three_hop | 500 | 236.10 µsfastest | 1.13 ms4.80× slower | 556.10 µs2.36× slower | 60.59 ms257× slower | 1.23 ms5.21× slower | 607.69 µs2.57× slower | 5.16 ms21.9× slower | LoraDB |
| traversal_two_hop | 500 | 165.58 µsfastest | 649.69 µs3.92× slower | 411.83 µs2.49× slower | 44.18 ms267× slower | 1.27 ms7.66× slower | 555.27 µs3.35× slower | 64.61 ms390× slower | LoraDB |
| traversal_undirected | 500 | 213.41 µsfastest | 492.51 µs2.31× slower | 494.10 µs2.32× slower | omitted | 1.76 ms8.26× slower | 637.72 µs2.99× slower | 4.20 ms19.7× slower | LoraDB |
| variable_length_path | 100 | 86.24 µsfastest | 2.64 ms30.6× slower | 210.43 µs2.44× slower | omitted | 809.87 µs9.39× slower | 605.82 µs7.02× slower | omitted | LoraDB |
| varlen_2_to_5 | 100 | 123.74 µsfastest | 3.90 ms31.5× slower | 274.33 µs2.22× slower | omitted | 986.89 µs7.98× slower | 578.29 µs4.67× slower | omitted | LoraDB |
| varlen_exact_5 | 100 | 56.89 µsfastest | 3.86 ms67.9× slower | 140.52 µs2.47× slower | omitted | 576.13 µs10.1× slower | 586.35 µs10.3× slower | omitted | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
traversal_undirectedSurrealDB- SurrealDB graph edges are directional; the undirected `-[:NEXT]-` pattern has no single like-for-like arrow form (forward and reverse are covered by traversal_one_hop and traversal_reverse).
variable_length_pathHelixDBSurrealDB- Range-bounded variable-length expansion isn't mapped; fixed depths are benched as recursive_depth2/3/5.
- SurrealQL recursive traversal takes a fixed depth `@{n}` (see recursive_depth2/3/5); a `1..3` range expanded from every start node has no like-for-like form.
varlen_2_to_5HelixDBSurrealDB- Range-bounded variable-length expansion isn't mapped (see variable_length_path).
- Range-bounded variable-length expansion from every start node; see variable_length_path.
varlen_exact_5HelixDBSurrealDB- Range-bounded variable-length expansion isn't mapped (see variable_length_path).
- Fixed-depth expansion from every start node; the anchored single-source equivalent is benched as recursive_depth5.
relation_filterGrafeo- grafeo's `create_edge` facade takes no edge properties, so the chain fixture has no `step` to filter on. memgraph/neo4j/kuzu seed `step` directly, so they do run this workload.
patterns(4)
WinnerLoraDB| Workload | Size | LoraDB | Kuzu | Grafeo | SurrealDB | Memgraph | Neo4j | HelixDB | Winner |
|---|---|---|---|---|---|---|---|---|---|
| edge_subquery_clause | 500 | 213.55 µsfastest | 465.09 µs2.18× slower | omitted | 18.19 ms85.2× slower | 1.18 ms5.52× slower | 605.92 µs2.84× slower | 4.12 ms19.3× slower | LoraDB |
| star_fanout | 1000 | 138.96 µsfastest | 296.68 µs2.13× slower | 321.31 µs2.31× slower | 29.94 ms215× slower | 1.62 ms11.7× slower | 579.41 µs4.17× slower | 2.75 ms19.8× slower | LoraDB |
| star_fanout_count | 1000 | 61.36 µsfastest | 290.42 µs4.73× slower | 193.38 µs3.15× slower | 20.35 ms332× slower | 500.19 µs8.15× slower | 590.08 µs9.62× slower | omitted | LoraDB |
| star_fanout_filter | 1000 | 112.53 µsfastest | 312.37 µs2.78× slower | 227.60 µs2.02× slower | 24.13 ms214× slower | 707.87 µs6.29× slower | 599.26 µs5.33× slower | 3.24 ms28.8× slower | LoraDB |
Workloads omitted only where the engine has no like-for-like equivalent — a missing scalar function, a different MERGE semantics, a reserved word. The reason is recorded with the workload, not silently dropped.
star_fanout_countHelixDB- The HelixDB enterprise-dev image rejects `count()` dynamic queries with `rate limit exceeded` (its other 9 workloads run fine); see aggregate_count.
edge_subquery_clauseGrafeo- grafeo's `create_edge` facade takes no edge properties, so the social fixture has no `strength` to filter on. memgraph/neo4j/kuzu seed `strength` directly, so they do run this workload.
Methodology
What this benchmark does and doesn’t say.
What’s measured
Mean iteration time on identical fixtures. Engines run the same query semantics where the language allows expressing them; where it doesn’t, the workload is marked omitted with the reason recorded next to it. Geometric mean is used at the group level so a single outlier doesn’t dominate the row.
What this isn’t
Not a production-scale TPC benchmark. Not a load test. Not a comparison of operational footprint or durability — every engine is run in-process or against a local server with the same warm fixture each iteration. Workloads are micro-benchmarks of the query pipeline, not of full systems.
Where to dig in
The full source — fixtures, harness, per-engine glue — lives in comparisons/. The report itself is generated from a single Criterion run; re-running the suite regenerates report.md, which this page is derived from.