Building a Distributed Key-Value Store in C++ (Part 2)
Table of Contents
💾 Persistence: Never Forget#
In Part 1, we built a simple in-memory key-value store. But if you kill the process, your data’s gone. That’s not acceptable in any serious system.
So in this part, we add persistence using an append-only log to record all changes to disk.
This means:
- Every 
putanddeloperation is logged - On startup, we replay the log to recover the in-memory state
 
This ensures durability and crash recovery, our KV store now “remembers”.
🧱 Append-Only Log#
Here’s how it works:
- Every 
put(key, value)writes a line likePUT key valueto a log file. - Every 
del(key)writesDEL key. 3. On startup, we replay the log file line-by-line to rebuild the in-memory store. 
🔍 Code Overview#
// In kvstore.cpp
void KVStore::append_log(const std::string& op, const std::string& key, const std::string& value) {
  if (log_out.is_open()) {
    log_out << op << " " << key;
    if (op == "PUT") {
      log_out << " " << value;
    }
    log_out << "\n";
    log_out.flush();
  }
}
A subtle but important thing here is flushing after every operation. This minimizes the chance of data loss if the process crashes.
Recovery on Startup#
void KVStore::load_from_log(){
  std::ifstream in(log_file_path);
  std::string line;
  while (std::getline(in, line)) {
    std::istringstream iss(line);
    std::string op, key, value;
    iss >> op >> key;
    if (op == "PUT") {
      iss >> value;
      store[key] = value;
    } else if (op == "DEL") {
      store.erase(key);
    }
  }
}
This function replays all logged operations to reconstruct the state on boot.
🧪 Testing with Catch2#
With persistence in place, it’s time to get serious about testing.
We’re using Catch2, a lightweight and expressive C++ testing framework. Thanks to CMake’s FetchContent, it’s easy to integrate:
# in CMakeLists.txt
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        v3.5.2
)
FetchContent_MakeAvailable(Catch2)
enable_testing()
add_subdirectory(tests)
# in tests/CMakeLists.txt
add_executable(test_kvstore test_kvstore.cpp)
target_include_directories(test_kvstore
  PRIVATE
  ${PROJECT_SOURCE_DIR}/src
)
target_link_libraries(test_kvstore
  PRIVATE
    kvstore_lib
    Catch2::Catch2WithMain
)
include(CTest)
# include(Catch)
# catch_discover_tests(test_kvstore)
🧪 Test Example#
// tests/test_kvstore.cpp
TEST_CASE("Basic KVStore operations") {
  KVStore store("test_store.log");
  store.put("a", "1");
  REQUIRE(store.get("a").value() == "1");
  store.put("b", "2");
  REQUIRE(store.get("b") == std::make_optional(std::string("2")));
  REQUIRE(store.del("a") == true);
  REQUIRE_FALSE(store.get("a").has_value());
}
Simple, readable, and effective.
🗺️ Updated Roadmap#
Phase 1: Local Store
✅ Done
Basic In-Memory KV Store
- Implemented a 
KVStoreclass - Supports 
put,get, anddeloperations - Command-line usage for demoing
 
- Implemented a 
 Phase 2: Persistence
✅ Done
Durability with Append-Only Log
- Append-only log on disk
 - Recovery by replaying log
 - Unit tests with Catch2
 
Phase 3: Networking
Next
Client-Server Communication
- Expose KVStore via TCP sockets
 - Define simple request/response protocol
 - Build interactive CLI tool
 
Phase 4: Multi-Node Architecture
Planned
Cluster Mode
Phase 5: Consensus
Planned
Leader Election and Coordination
Phase 6: Testing & Resilience
Planned
Hardening the System
📌 What’s Next?#
Now that we can persist data across restarts, we’re ready to make the leap from single-process to client-server architecture. That means:
- Opening a TCP port
 - Receiving requests
 - Sending back responses
 
In the next post, I’ll build the first version of a networked KV store, setting the stage for a truly distributed system.
There are no articles to list here yet.