Distributed key-value store with Raft consensus and a custom LSM storage engine, written from scratch in C++20.
- Raft consensus — leader election, log replication, persistent state, snapshots + log truncation, joint-consensus membership changes (§6)
- LSM storage engine — write-ahead log, in-memory MemTable, on-disk SSTables, MANIFEST-based crash recovery
- Real TCP transport between nodes — Boost.Asio + length-prefixed protobuf framing
- Linearizable client API via
knotctl(Put / Get / Delete) with automatic leader redirect - Wing-Gong linearizability checker — every recorded client history must admit a valid linearization, verified under chaos
5-node cluster, 8 concurrent writers, Apple silicon, Release build:
| Workload | Throughput | p99 latency |
|---|---|---|
| Pure writes | 2,092 ops/sec | 5.1 ms |
| Mixed 50/50 put/get | 2,131 ops/sec | 5.1 ms |
| Under leader-kill chaos | 1,637 ops/sec | 6.0 ms |
Median leader-failover recovery: 259 ms, p99 295 ms, success rate 99.94% while killing the leader every 2 seconds. Full methodology in docs/benchmarks.md.
188 tests, all green under three CI builds:
| Build | What it catches |
|---|---|
| default | functional correctness |
| ASan + UBSan | heap-use-after-free, leaks, undefined behavior |
| TSan | data races |
Includes:
- Unit tests per Raft module (log, persister, wire, node, configuration)
- 3-node membership-change end-to-end tests
- Multi-threaded chaos stress test with partition/heal cycles
- Linearizability test that feeds a recorded chaos history into the
knot::lincheck::Check()checker
Prereqs: cmake, ninja, clang (or recent Apple clang), and
vcpkg.
git clone https://github.com/microsoft/vcpkg ~/vcpkg && ~/vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=~/vcpkg
cmake --preset=release
cmake --build build/release
ctest --test-dir build/releaseOther presets: default (Debug), asan (ASan+UBSan), tsan.
Start 5 daemons (separate terminals or & in the background):
./build/release/knotd --id=n1 --listen=127.0.0.1:7001 \
--peer=n2@127.0.0.1:7002 --peer=n3@127.0.0.1:7003 \
--peer=n4@127.0.0.1:7004 --peer=n5@127.0.0.1:7005 \
--client-listen=127.0.0.1:8001 --data-dir=./data/n1
# ...repeat for n2..n5 with their own listen/client/data-dir portsDrive it with knotctl:
SERVERS=n1@127.0.0.1:8001,n2@127.0.0.1:8002,n3@127.0.0.1:8003,n4@127.0.0.1:8004,n5@127.0.0.1:8005
./build/release/knotctl --servers=$SERVERS put hello world
./build/release/knotctl --servers=$SERVERS get hello # → world
./build/release/knotctl --servers=$SERVERS delete helloRun the benchmark harness:
./build/release/knot-bench --workers=8 --ops=10000
./build/release/knot-bench --workload=recovery --workers=8 --ops=20000See docs/architecture.md for the per-node stack, write/read paths, threading model, and the design decisions that shaped each layer.
MIT — see LICENSE.