This file provides guidance to AI coding agents working in this repository.
mkdir build && cd build
cmake -G Ninja <SOURCE_DIR>
ninja
# or: ninja fdbserver fdbcli fdbclient (specific targets)Key cmake options:
-DCMAKE_BUILD_TYPE=Debug(default Release)-DUSE_WERROR=ONfor development-DOPEN_FOR_IDE=ONto generate IDE project without building
macOS: add -DBUILD_SWIFT_BINDING=OFF if Swift issues arise.
Linux with Clang: CC=clang CXX=clang++ cmake -DUSE_LD=LLD -DUSE_LIBCXX=1 -G Ninja ..
Unit tests use TEST_CASE("/path/to/test") macros (defined in flow/include/flow/UnitTest.h). Prefer the narrowest dedicated unit-test target instead of fdbserver -r unittests; these targets compile and run faster. Use fdbclient_test for client tests or the relevant fdbserver_<library>_test target defined via add_fdbserver_unit_test (for example, fdbserver_core_test, fdbserver_kvstore_test, or fdbserver_storageserver_test). Find the applicable target in the affected component's CMakeLists.txt:
ninja fdbclient_test
bin/fdbclient_test # all tests in the target
bin/fdbclient_test -f "<test-prefix>" # tests matching a prefix
ninja fdbserver_core_test
bin/fdbserver_core_testUse fdbserver for simulation or broader end-to-end validation, not as the default unit-test runner.
Simulation tests use TOML workload definitions in tests/fast/, tests/slow/, etc.:
bin/fdbserver -r simulation -f tests/fast/CycleTest.tomlVia ctest:
ctest -L fast # all fast tests
ctest -R "StatusDuringOutage" # by name patternEnable simulation tests in cmake: -DENABLE_SIMULATION_TESTS=ON
Adding new tests:
- For a new otherwise-unreferenced translation unit containing
TEST_CASE, add aforceLinkXxxTests()stub and call it fromfdbserver/workloads/UnitTests.cpp, or the linker can drop the tests. - Register new
.tomlor.txttests undertests/intests/CMakeLists.txtviaadd_fdb_test.configure_testing(... ERROR_ON_ADDITIONAL_FILES)andverify_testing()should catch unregistered files during CMake configuration.
Joshua: bulk correctness/simulation testing runs on the internal Joshua cluster against optimized Release builds. Local fdbserver -r simulation runs are for iteration; large-scale shake-out happens on Joshua.
Trace logs: simulation and test runs emit trace.*.xml (or .json) files in the run directory. TraceEvent(...) is the canonical logging mechanism — search trace files by event name when debugging a simulation failure.
- A
SevError(Severity 40)TraceEventfails a simulation/Joshua run. UseSevWarnAlwaysfor "shouldn't happen but non-fatal." Errors carrying an injected fault are auto-downgraded; genuineSevErrors are not. Expected errors must be suppressed/allowlisted, never emitted atSevError. - Keep a
TraceEventsmall: any singledetail()value over 495 bytes is truncated (...), and an event whose fields total over 4000 bytes is dropped entirely and re-logged asTraceEventOverflow— atSevErrorin simulation, so an oversized event fails the test. Chunk long payloads (joined lists, command lines, serialized blobs) across multiple events. - The
Timefield isnow(), which does not advance betweenco_awaits, so spans within one run-loop slice read as zero.g_traceBatchevents also carryMonotonicTimefor this.
FoundationDB is a distributed ordered key-value store with strict serializability. The codebase is organized into ~12 subsystems. For background on subsystems before diving into code, the design/ directory holds human-authored design docs and design/AI-generated/ holds subsystem maps and per-subsystem diagrams (start with design/AI-generated/foundationdb_subsystem_map.md).
FDB uses cooperative single-threaded concurrency. Asynchronous functions use
standard C++ coroutines with co_await and co_return in ordinary .cpp and
.h files. See design/coroutines.md for coroutine and cancellation semantics.
Key types: Future<T>, Promise<T>, PromiseStream<T>, Reference<T> (ref-counted pointer), Optional<T>, ErrorOr<T>, Arena (region-based allocation).
- C++ coroutines:
co_awaitis not allowed inside acatchhandler. Capture the error, exit the catch, thenco_awaitoutside. - Errors are integer codes (
flow/include/flow/error_definitions.h), not exceptions with messages. When youcatch (Error& e), re-throwactor_cancelled(and never silently swallowbroken_promise) — eating cancellation causes hangs and leaks. Transaction retry goes throughtr.onError(e), not a bare loop. StringRef/KeyRef/ValueRefare non-owning views into anArena. Returning or storing one past its arena's lifetime is a dangling-reference bug; useStandalone<>(orKey/Value) when you need to own the bytes.a && bonFuture<Void>isquorum(x, 2, 2), not a join: the first error completes it and drops the other input, cancelling it if nothing else holds it, and an already-failedlhsdiscardsrhsunreferenced. Never accumulate withf = f && g; usewaitForAllReady()to wait for everything regardless of errors.Subspace::range()spansprefix + '\x00'toprefix + '\xff', so it excludes the subspace's own key: a value written ats.pack(x)is not returned by scannings.get(x).range().
flow/— Async runtime, event loop, tracing, deterministic random, arenasfdbrpc/— Endpoint-addressed RPC, peer management, failure monitor, Sim2 (deterministic simulation network)fdbclient/— Transaction API (NativeAPI.cpp), read-your-writes (ReadYourWrites.cpp), location cache, multi-version clientfdbserver/— All server roles, organized by subdirectory:clustercontroller/— Leader election, role recruitment, ServerDBInfo broadcastingcoordinator/— Paxos-based coordination state (generation registers)commitproxy/,grvproxy/,resolver/— Transaction commit pipelinetlog/,logsystem/— Durable mutation log (tag-partitioned)storageserver/— Serves reads, applies mutations from TLogskvstore/— Pluggable storage engines (RocksDB, SQLite, memory/DiskQueue)datadistributor/— Shard management, team building, data movementratekeeper/— Back-pressure and throttlingworkloads/— Simulation test workloads
fdbserver -r simulation runs the entire cluster in a single process using Sim2, a deterministic simulated network. buggify() calls (flow/include/flow/Buggify.h) inject faults (delays, failures, corruption). Tests are TOML files that compose workloads. This is FDB's primary testing strategy.
- Determinism is per seed and binary. Any code change shifts the
deterministicRandom()sequence, so the same seed on a patched binary is a different execution, not an A/B comparison. Reproduce a failure on the unpatched binary, and judge a fix by failure rates over many seeds. - A continuation can resume with a different simulated process current than the one that started waiting, e.g. when a future is shared between processes on one simulated machine. Code that binds to
g_simulator->getCurrentProcess()should capture it on entry andco_await g_simulator->onProcess(...)back before binding.
Runtime-tunable parameters are declared in fdbserver/core/include/fdbserver/core/Knobs.h, fdbclient/include/fdbclient/Knobs.h, and flow/include/flow/Knobs.h (UPPER_CASE names). Access them via SERVER_KNOBS, CLIENT_KNOBS, or FLOW_KNOBS. Defaults and buggification ranges live in fdbserver/core/ServerKnobs.cpp, fdbclient/ClientKnobs.cpp, and flow/Knobs.cpp.
Before changing a serialized type that persists on disk, inspect its serializer() and adjacent versioning or downgrade comments. Do not reorder, remove, or change encoded fields without the appropriate version gate or migration and compatibility coverage.
- Variables, functions, methods: camelCase (
smoothTotal,getWorkerForRole) - Classes, structs, enums: PascalCase (
StorageServer,RecoveryState) - Knobs/constants: UPPER_CASE (
MAX_BATCH_SIZE,VERSIONS_PER_SECOND) - Globals:
g_prefix for important ones (g_network,g_simulator)
clang-format is used. Python code uses black and flake8 (pre-commit hooks: pip install pre-commit && pre-commit install).
Edit tracked .cpp and .h sources, not generated output under the build directory.
Every new .cpp / .h file starts with the standard Apache 2.0 license block, with the filename on line 2 and the current year on the copyright line. Copy from any existing file in the tree (e.g. flow/Knobs.cpp). Add file-purpose comments after the license block, not in place of it.
General guidance: comments should maximize clarity. They should be clear without being verbose. They should be concise without being cryptic.
Scope: the guidance here mainly governs inline and implementation comments. Comments on public-facing APIs are contract statements and must be clear, precise, and complete.
Audience: assume the readers of FDB are senior engineers fluent in asynchronous and concurrent programming, distributed systems, and stateful complex systems generally. Calibrate "non-obvious" to that reader — do not explain the language, the standard library, Flow/actor idioms, or established patterns they already know. Reserve comments for what such a reader could not quickly infer from the code itself.
Implementation comments should focus on important, non-obvious fundamental rationale, motivation, and explanation of extremely subtle phenomena. Writing down important invariants — and the preconditions a piece of code requires of its caller (e.g. a lock that must be held, or a version that must already be durable) — is also a good idea.
Do not write comments about mundane day-to-day development refactorings — junk like // This used to be part of xyzFunc in Foo.cpp but we moved it here so that aaaFunc could also call it.
Do not write caller-location / "happening elsewhere" comments — narration of who invokes this code or where it is used ("called from X", "used by Y"). They duplicate what a search answers instantly and rot when callers change; describe what the code requires and guarantees on its own terms. Stating a real invariant is fine — "recovery is the only path that repairs" — naming the callers that happen to exist today is not.
After writing or changing a bunch of code, make a pass over it one last time to review the comments. Ask yourself: am I rambling? Am I repeating what the code is showing? Am I just telling stories here? Is the information I'm writing going to be relevant and important a week from now, a month from now, a year from now? Is it genuinely essential and non-obvious from the code itself?
When reviewing, state explicitly: what the change is for, whether it is correct, bugs, omissions, better approaches, and a clear LGTM / no / not-yet. Read a construct's history (git log -L, git blame) before proposing to change something that looks odd.
PRs target main. Release branches receive cherry-picks rather than direct PRs — don't open backport PRs without confirming first.
Before a design doc is considered finished (not at the start), reconcile it against design/design-doc-template.md.