Storage
Layout
<data_dir>/
LOCK flock: one daemon per data dir
catalog.bolt collection definitions
cursors.bolt watch consumer positions
blobs/ large values, content-addressed (sha256)
collections/
<name>/
000001.ndjson ... WAL (engine mode), see wal.md
segments/ Parquet segments (derived)
meta.bolt checkpoint, segment list, file cursors (derived)
quarantine/<name>.ndjson lines that could not be applied (direct mode)Look at it with ls; do not edit it by hand while the daemon runs.
Write modes
Engine mode. Only ndb writes, through the WAL. One writer per collection gives serializable writes; if_version gives optimistic concurrency.
Direct mode. The apps append to their own files, ndb only reads them. Rules for the apps:
- One writer per file (for example
calls-<host>-<pid>.ndjson). - One line per
write(), ending with\n. - An update or delete is a new line. With
idset, the last line for an id wins.{"<id>": "...", "_deleted": true}removes a document.{"_batch":[...]}is all or nothing. - Rotation is fine. Do not truncate a file that is in use.
ndb finds files by the glob, with fsnotify and a 5 s poll (bind mounts and overlays can miss events). It keeps a cursor per file (inode, offset, fingerprint of the first KiB) and reads only up to the last \n.
Segments and compaction
The recent rows live in memory. They are sealed to an immutable Parquet segment when they reach 64 MiB or 10 minutes since the last seal. Segments hold per-row-group min/max (zone maps), so queries skip what cannot match. DuckDB, Spark and pandas can read them. When 4 or more small segments are adjacent, compaction merges them, and drops old versions, tombstones, expired rows and rows past retention.
Rebuild and recovery
At start the daemon takes the lock, reads the checkpoint, replays the WAL after it, continues each source file from its cursor, and removes temporary files and segments the checkpoint does not list. Segments, indexes and checkpoints can be deleted: ndb builds them again from the NDJSON.
Derived files are written as tmp, fsync, rename, fsync of the directory. All file access goes through an fs interface so tests can inject faults.
Limits
max_line_sizeper collection: default 1 MiB, maximum 16 MiB. Engine mode:line_too_large. Direct mode: the line goes to quarantine (only its first 4 KiB is copied).- Large binary values: use the blob store. The document keeps
{"$blob":"sha256:...","size":N,"type":"..."}. - Nested objects become columns up to depth 4. A value of another type than the column keeps the exact document in
_raw;findalways returns the exact stored document.