NornicDB vs Neo4j — Tradeoffs
Snapshot of where NornicDB lands against Neo4j on the Northwind 48k-product / 48k-order benchmark, after the recent storage and codec work (id dictionary, compact edge codec, MVCC head index, property-key dictionary). Numbers are from benchmark_northwind_vs_neo4j.sh, 10 iterations after 2 warmup, Bolt protocol via neo4j-go-driver.
At a glance
| Dimension | NornicDB | Neo4j | Delta | Who wins |
|---|---|---|---|---|
| Mean query latency | 0.23 ms | 98.64 ms | -99.8% (432.38x ratio) | NornicDB |
| Throughput (ops/s) | 17.70 | 7.76 | 2.28× higher | NornicDB |
| Benchmark wall-clock | 14.0 s | 31.6 s | 2.26× faster | NornicDB |
| Energy per workload | 118 J | 265 J | 55% less | NornicDB |
| GPU avg power | 40 mW | 95 mW | 58% less | NornicDB |
| CPU/package avg power | 8.96 W | 8.70 W | +3% | tie |
| Peak memory used | 32.5 GiB | 33.4 GiB | -3% | tie |
| Seed duration | 6.80 s | 5.75 s | +18% | Neo4j |
| Raw data files | 133 MiB | 51 MiB | 2.62× larger | Neo4j |
| Write-ahead log | 260 KiB | 144 MiB | 555× smaller | NornicDB |
Total disk (du) | 134 MiB | 204 MiB | -34% | NornicDB |
Tradeoffs by category
| Category | NornicDB strength | Neo4j strength | Why |
|---|---|---|---|
| Read latency | sub-millisecond on every query class — customer_category_distinct_orders is 99.9% lower than Neo4j (0.25 ms vs 227 ms; 908x ratio) | — | Numeric-ID secondary indexes (8B per ref instead of 40-50B UUID strings), compact edge codec, in-memory dictionary lookups, MVCC head index pointing at current bodies without scan |
| Write latency / seed | — | 18% faster on the 168k-edge seed | Neo4j is in-place record-store mutation; NornicDB writes the body, the id-dict forward+reverse, the property-key forward+reverse, six secondary indexes, and an MVCC head per record. Acceptable cost for the read-side win |
| Energy efficiency | 55% less energy per equivalent workload, 58% lower GPU usage | — | Mostly a function of finishing 2.26× faster at equal package power. GPU delta is real — NornicDB doesn't push query work onto the GPU |
| Body bytes on disk | — | Record-store layout is denser per record | LSM amplifies (multiple SST levels, value log preallocation, MVCC archives). V2 property-key tokenization recovered ~20% of the body cost but the LSM gap is structural |
| Total disk footprint | 34% less when WAL is included | — | Neo4j's WAL is 144 MiB (transactional log durability); NornicDB's is 260 KiB (measured footprint depends on WAL retention and sync configuration; see durability) |
| Operational simplicity | Single binary, embeds Badger directly, no JVM | Mature ops tooling, established backup/restore, broad cloud presence | Different points on the maturity curve — NornicDB is leaner; Neo4j has the runway behind it |
| Multi-tenant isolation | Per-namespace id space and property-key dict; one tenant's schema sprawl can't exhaust another's | Shared store, single global token table | NornicDB designed for multi-tenant from the namespace prefix up |
| Schema flexibility | No declared schema required; properties allocate dictionary IDs on first sight | Optional indexes / constraints surface explicitly | Neo4j's explicitness helps planner / DBA workflow; NornicDB stays out of your way |
| Memory footprint at idle | similar (~32 GiB peak system-wide during the bench, broadly tied) | similar | Neither engine is the memory bottleneck on a 100k-entity workload |
| Maturity / ecosystem | — | Cypher reference implementation, broad tooling, Bloom, APOC, GDS, hosted offerings | Hard tradeoff to argue against — Neo4j has been in production for 15+ years |
Where the wins come from (architecturally)
| Lever | Effect |
|---|---|
idDictionary | 40-50B string IDs → 8B numIDs in every secondary index. Indexes shrink by ~80%, fewer cache misses on traversal |
| Compact edge codec | Drops the per-record gob/msgpack field-name overhead (StartNode, EndNode, Type, etc.). Endpoints are 8B numIDs; scalar metadata is fixed-width binary |
| MVCC head index | Reads of "current state" hit a single pointer key instead of scanning version chains. Latency on point lookups is dominated by Badger's block cache, not the engine |
| Property-key dictionary (V2) | Property names tokenized to per-namespace varint IDs. ~20% body size reduction on shared schemas |
| Per-txn counter batching | Dictionary counter writes amortize to one Set per dirty namespace per commit, not one per allocation. Seed-heavy workloads pay 2 counter writes total instead of N |
| Badger value log | Large blob values (embeddings, descriptions) live in the value log out of the LSM key path. Block cache only holds the LSM keys + small inline values |
Where Neo4j is structurally ahead
| Lever | Effect |
|---|---|
| Fixed-size record store | Each entity is N bytes wherever it lives. No LSM amplification, no value log, no MVCC archive. Smaller raw-data bucket |
| In-place mutation | Updates rewrite the same record in place. NornicDB's MVCC archives the prior body for snapshot isolation, paying disk for read consistency |
| Mature query planner | Multi-table cost-based optimization, hint syntax, dump/restore, online indexes |
| Tooling depth | Bloom for visualization, APOC for utility procedures, GDS for graph algorithms, neo4j-admin for ops |
Scalability outlook
| Scale axis | Expected behavior |
|---|---|
| Linear corpus growth (more nodes/edges of same shape) | Latency advantage holds or widens. Numeric-ID indexes don't slow down with relationship count; Neo4j traversal does |
| Schema growth (more property names) | Dictionary stays tiny — ~30B per ever-used name. Even 10k distinct names per namespace is well under 1 MB of dict |
| Multi-tenant scale (more namespaces) | Per-namespace dict isolation prevents cross-tenant interference. Memory cost is O(distinct names × namespaces); negligible until tenant count is in the thousands |
| Hot-data working set vs total store | Badger's block cache is the dominant factor. Smaller bodies (V2) mean more records fit in cache; V1→V2 directly improves cache hit rate |
| WAL retention | The recorded WAL sizes describe this run. Future growth depends on snapshots, retention and workload; they are not a durability or long-term cost guarantee. |
Honest cost line
- One-way storage upgrade. Moving an existing NornicDB store to V2 requires
--upgrade-storageand a backup. No downgrade path. - First-open cost on V1 stores. Eager rewrite is bounded by corpus size — seconds on this 48k seed, hours on a 1B-record store. Resumable and crash-safe, but not free.
- Less mature than Neo4j on cypher edge cases. Bolt protocol is implemented to the spec the benchmark exercises; less-common Cypher constructs may need feature parity work.
Bottom line
NornicDB trades a 2.62x larger raw-data bucket for 99.8% lower mean query latency (432.38x ratio), 55% less energy per workload, and 99.8% less WAL space (568.5x ratio). Total disk footprint favors NornicDB once WAL is included. The latency and energy gains come from the index architecture (numeric IDs, compact codecs, MVCC head pointers); the body-size delta is structural to LSM vs record-store and is partially closed by the property-key dictionary.
For workloads dominated by reads, traversals, and aggregations on graph data — i.e. most graph-database use cases — the architectural wins compound. For workloads where storage cost per record matters more than query latency, the gap is real but narrowing, and the WAL difference may flip the balance over time.