Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing and Measurement

A database earns trust two ways: by being unable to give wrong answers, and by understanding its own performance. This chapter covers both mechanisms — the correctness stack (unit suites, conformance, crash safety, integrity) and the measurement discipline that the rest of this book has been quietly citing.

The correctness stack

Unit and end-to-end suites live where the code lives: storage and graph invariants in marsdb-graph’s tests, query shape and mechanics in marsdb-query’s, and full workloads at the marsdb crate level — including a suite that runs the LDBC Social Network Benchmark’s short-read queries via real parameter substitution against a shared fixture, asserting exact results, not “doesn’t panic.” Stress tests (50k-node chains, 10k-fanout supernodes with detach-delete, 20k random operations checked against an in-memory oracle) are #[ignore]d by default and run explicitly.

Conformance is the openCypher TCK, run by marsdb-tck: a harness that parses the official Gherkin feature files (vendored as a git submodule), builds each scenario’s initial graph, runs the scenario’s query, and compares against the expected table with TCK value semantics. Every scenario lands in one of five outcomes — pass, wrong result, unexpected behavior, parse-rejected, or runner-unsupported — because “how it fails” is more informative than a pass rate alone: a wrong result is a correctness bug; a clean parse rejection of an unimplemented feature is a scope decision. MarsDB currently passes 3,880 of 3,880 scenarios, and the coverage table (CYPHER_COVERAGE.md) is generated from real runs, not maintained by hand.

The TCK’s deepest value showed up in this book repeatedly without being named: several of the subtlest behaviors in the executor — type(r) surviving DELETE r, edge isomorphism reaching across a pattern into a variable-length hop’s BFS, MERGE accepting a bound-variable property — were found by scenarios, and the code comments cite the exact ones. A conformance suite is a machine-checkable spec, and its scenarios reach corners that first-principles test-writing does not.

Crash safety has its own harness (marsdb-crash-harness), and its scope is stated in its module docs: this is level-1 crash safety — process death while the OS survives and the page cache remains intact — and explicitly not power loss (which loses the page cache and would need fault injection to test). Within that scope, a child process commits a random number of single-CREATE transactions with a monotonically numbered counter; the parent SIGKILLs it at an unpredictable moment, reopens the file cold, and asserts a purely structural invariant — the surviving counter values must be exactly a contiguous prefix {1..K}. No gaps (no half-applied transaction), no duplicates (no doubly-recorded commit). The parent never synchronizes with the child’s commit progress, because the invariant holds regardless of where the kill lands — an assertion designed so that racing is not a bug in the test.

The integrity checker (chapter 4) doubles as the final oracle: stress and crash tests can end with a full logical validation of every cross-table invariant, converting “the test passed” into “and the file is coherent.”

The measurement discipline

BENCHMARKS.md is the repository’s ledger of numbers, and its rules are as much a part of the engineering culture as any code:

  • Only measured numbers. Nothing is estimated, extrapolated, or “expected to be fast.” A feature without a benchmark has no performance claim — the file has no placeholder rows.
  • Provenance on every number. Hardware, date, build profile, and the exact reproduction command (cargo bench -p ..., using Criterion) accompany each table.
  • Caveats are stated, not buried. The concurrent-read scaling table (1.26x at 2 threads up to 1.87x at 8, then a plateau) is followed by two known unisolated factors that may cap it, and the conclusion is scoped to what the data supports: concurrency reliably beats sequential, which is what the feature is for. An explicit “scope of these numbers” section lists what has not been measured — file-backed fsync-pressure throughput, unbenchmarked operators — so absence of evidence is visible instead of silent.
  • End-to-end checks bracket the micro-benchmarks. A real dataset (28,863 nodes, 166,261 relationships, loaded from plain Cypher) is the load/query/update/delete lifecycle gate, re-run as internals change; micro-benchmarks alone can miss regressions that only compose at scale.

The payoff of running real workloads is not just numbers — it is bug discovery. The lifecycle benchmark directly surfaced two planner defects (an IndexSeek that never fired for row- and parameter-bound equalities, and a multi-hop pattern’s start-node WHERE never reaching the scan it should narrow), both fixed because a measurement looked wrong. A benchmark suite that only confirms expectations is underused.

Continuous integration

CI runs the test suite on Linux, macOS, and Windows; formatting and clippy as a gate; coverage collection; a dependency security audit; and a bindings job that builds the Python extension and runs its tests against the workspace — plus a build of the C ABI with the Arrow feature, which is the pre-merge check protecting the out-of-repo Go binding (its own CI builds this repository’s C ABI from main and would otherwise discover breakage only after merge).

The final chapter collects the measurements that changed decisions — including those that led to a feature’s removal.