Skip to content

NEDB v2.2.0 — Performance Sprint

Choose a tag to compare

@ethgr0wth ethgr0wth released this 17 Jun 20:59
4628df9

"In-memory DAG. WAL write buffer. Bindings on v2. Batch at 4,798/s."


Install

pip install --upgrade nedb-engine        # 2.2.0 — PyO3 bindings now use v2 DAG
npm install nedb-engine                   # 2.2.0 — napi-rs bindings now use v2 DAG
pip install --upgrade nedb-engine-client  # 1.2.0
npm install nedb-engine-client            # 1.2.0

What's new

🦀 Db::in_memory() — zero-disk DAG

Pure in-memory database: HashMap-backed ObjectStore, IdIndex, and GraphStore. Zero file I/O, sub-microsecond puts, instant startup. Perfect for tests, hot cache layers, and ephemeral sessions.

# Python (via PyO3)
from nedb._native import NedbCore
db = NedbCore()             # in-memory — no files
db.put("items", "1", '{"x":1}')
db.query('FROM items LIMIT 5')

# TypeScript (via napi-rs)
import { NedbCore } from "nedb-engine"
const db = new NedbCore()   // in-memory
db.put("items", "1", JSON.stringify({x:1}))

🏁 NEDBD_MEMORY=1 — in-memory server mode

Start the entire nedbd daemon in memory — no files created anywhere:

NEDBD_MEMORY=1 nedbd --dag --data /ignored
curl http://127.0.0.1:7070/health
# {"memory": true, "engine": "dag", "ok": true}

Banner shows: memory = yes — all data lost on exit. Health endpoint includes "memory": true.

⚡ WAL write buffer — id-index off the hot path

id_index.set() previously called fs::rename() on every PUT — one file rename per document, serialized across concurrent workers. Now:

  • set() writes to a DashMap buffer only (zero I/O, lock-free, ~nanoseconds)
  • Background ticker calls flush_write_buf() every 1s — Rayon parallel flush to disk
  • get() checks WAL buffer first (latest value), then disk
  • list_ids() merges disk + WAL, applies tombstones

🔀 spawn_blocking — parallel object I/O

db.put() wraps the object file write in tokio::task::spawn_blocking so concurrent PUT requests run on the blocking thread pool (up to 512 threads) instead of blocking the tokio async executor. Correct architecture for Linux where concurrent file writes perform well.

🔄 PyO3 + napi-rs bindings → v2 Db API

Both native binding crates (nedb-py, nedb-node) now use nedb_core_v2::Db:

  • pip install nedb-engine + native wheel → Python gets v2 DAG engine natively (no HTTP)
  • npm install nedb-engine + native addon → Node.js gets v2 DAG engine natively
  • Same API surface as v1 — existing Python/Node code works unchanged
  • put() extracts caused_by/valid_from/valid_to from doc for DAG provenance
  • link()/unlink() stored as __links__ docs for NQL TRAVERSE compatibility
  • flush() now calls flush_all() (WAL + MANIFEST)

🚀 503 fix — empty DB instant ready

New or just-created databases (0 objects) previously had a tiny window where startup_ready = false while a background thread confirmed "nothing to scan". Any write landing in that window got a 503. Fixed: empty DB sets startup_ready = true immediately without spawning a thread.


Benchmark (Intel iMac, 10k writes / 10k reads)

Operation v2.1.0 v2.2.0 Change
Batch writes (500/req) 4,019/s @ 0.35ms 4,798/s @ 0.14ms +19% 🔥
Sequential writes 492/s @ 2.7ms 504/s @ 1.8ms +2%
Point-lookup reads 542/s @ 3.2ms 475/s @ 2.1ms ≈ flat
ORDER BY queries 583/s @ 2.1ms 573/s @ 2.4ms ≈ flat
Verify 30k objects 997ms 939ms +6%
503 on warmup ⚠️ yes ✅ gone fixed

Note: concurrent write gains require Linux (ext4/io_uring). macOS APFS serializes concurrent file writes at the kernel level regardless of threading.


Full changelog

  • feat(db): Db::in_memory() — zero-disk DAG (ObjectStore, IdIndex, GraphStore all HashMap-backed)
  • feat(server): NEDBD_MEMORY=1 / memory_mode — in-memory server flag, health field, banner line
  • perf(index): WAL write buffer — id_index.set() writes to DashMap, Rayon-parallel flush every 1s
  • perf(server): spawn_blocking for PUT — object file I/O runs on blocking thread pool
  • feat(bindings): PyO3 bindings → nedb_core_v2::Db — native Python DAG engine
  • feat(bindings): napi-rs bindings → nedb_core_v2::Db — native Node.js DAG engine
  • fix(startup): empty DB sets startup_ready = true immediately — eliminates 503 on first write
  • fix(compile): use std::sync::Arc in store/index/graph (missing import)

Links


© INTERCHAINED, LLC × Vex (Claude Sonnet 4.6)