Redis Clone (C++)
A from-scratch reimplementation of Redis's core: a RESP protocol parser, an in-memory key-value store with TTL/expiry via lazy deletion, and a socket-based server handling concurrent clients.
Highlights
- $ Implemented a RESP (REdis Serialization Protocol) parser to handle the wire format used by real Redis clients.
- $ Built a key-value datastore wrapping std::unordered_map inside a dedicated Datastore class.
- $ Added TTL/expiry support using std::optional<std::chrono::steady_clock::time_point>, with lazy deletion on access.
- $ Structured the project as a proper multi-file C++ build — header/source splits, one-definition rule — managed with CMake.
- $ Built the networking layer to handle multiple concurrent client connections.
Architecture
- $ set(key, value, expiry)
- $ get(key) -> std::string
- $ has_key(key) -> bool
- $ TTL: lazy expiry on access
Trade-offs & decisions
Lazy deletion vs. active expiry sweeping
Expired keys are checked and evicted on access via std::optional<steady_clock::time_point>, rather than run by a background sweep thread. Simpler and avoids extra synchronization, at the cost of expired-but-unaccessed keys sitting in memory a little longer than strictly necessary.
Encapsulated Datastore class vs. raw map exposure
Wrapping unordered_map in a Datastore class keeps TTL logic and future thread-safety changes (locking, sharding) contained to one place, at a small cost of an extra indirection layer for every read/write.
Code excerpt
// Accept loop and Client Handling
// server_fd is established earlier in main.cpp
while(true){
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
std::cout << "Waiting for a client to connect...";
int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, (socklen_t *) &client_addr_len);
std::thread worker(handle_client, client_fd, std::ref(data));
worker.detach();
}
void handle_client(int client_fd, Datastore& data)
{
char buffer[1024];
std::cout << "[Thread " << std::this_thread::get_id() <<"] Client Conneted via socket: " << client_fd << std::endl;
while(true){
std::memset(buffer, 0, sizeof(buffer));
int bytes_received = recv(client_fd, buffer, sizeof(buffer), 0);
if (bytes_received <= 0){
std::cout << "[Thread " << std::this_thread::get_id() <<"] Client disconnected or error occured";
}
std::cout << "Processing message..." << std::endl;
std::vector<std::string> message {parse_bulk_string(buffer)};
std::string response {handle_received(message, data)};
std::cout << response << std::endl;
send(client_fd, response.c_str(), response.size(), 0);
}
close(client_fd);
}
Live demo
This runs the resp_parser.cpp, command_handlers.cpp,
and datastore.h compiled to WebAssembly. The TCP socket
layer from main.cpp can't run in a browser, so it's replaced with a
direct call straight into the compiled module.