Built this as a learning project to understand how a basic key-value store works under the hood. It's an in-memory hashmap with an LRU cache for faster repeated reads and an append-only log file for persistence across restarts.
- Store and retrieve key-value pairs (strings only for now)
- LRU cache so repeated reads are fast
- Persists to an append-only log file, recovers on restart
- Thread safe with a mutex
- Optional TCP server mode (simple line protocol)
src/
kv_store.h/cpp - main store implementation
lru_cache.h - cache using list + hashmap trick
main.cpp - demo showing basic usage
tcp_server.h/cpp - minimal TCP server wrapper
tests/
basic_tests.cpp - unit tests
stress_test.cpp - hammers it with threads to find bugs
benchmark/
benchmark.cpp - measures how fast operations are
Need g++ with C++11+ (and threading support). I used MinGW-w64 on Windows.
cd "c:\Users\Bhawya Rawal\Documents\Multithreaded Key-Value Store"
# Build main executable
C:\mingw64\bin\g++.exe -std=c++11 -g -o build\kvstore.exe src\main.cpp src\kv_store.cpp src\tcp_server.cpp -lws2_32 -pthread
# Build tests
C:\mingw64\bin\g++.exe -std=c++11 -g -o build\tests.exe tests\basic_tests.cpp src\kv_store.cpp -lws2_32 -pthread
# Build stress test
C:\mingw64\bin\g++.exe -std=c++11 -g -o build\stress.exe tests\stress_test.cpp src\kv_store.cpp -lws2_32 -pthread
# Build benchmark
C:\mingw64\bin\g++.exe -std=c++11 -g -O2 -o build\benchmark.exe benchmark\benchmark.cpp src\kv_store.cpp -lws2_32 -pthreadOn Linux/macOS, drop -lws2_32.
# Run demo
.\build\kvstore.exe
# Run as TCP server (on port 8080)
.\build\kvstore.exe --server 8080
# Run tests
.\build\tests.exe
# Run stress test
.\build\stress.exe
# Run benchmark
.\build\benchmark.exeKVStore store(100, "mydata.log");
store.put("user:1", "john");
bool found;
string name = store.get("user:1", found);
if (found) {
cout << name << endl; // prints "john"
}
store.remove("user:1");Start a server on port 8080:
./kvstore.exe --server 8080 data.log
Connect with telnet or any TCP client:
telnet 127.0.0.1 8080
Commands (one per line):
PUT name Bhawya
GET name
DEL name
SIZE
QUIT
Why hashmap? O(1) lookups. Could use a tree for sorted keys but didn't need that here.
Why LRU cache? If you keep reading the same keys, they stay in cache and you skip the hashmap lookup entirely. Used the splice trick with std::list to make it O(1).
Why append-only log? Simple crash recovery: append PUT: / DEL: lines, replay on startup.
Thread safety? Uses a single mutex to make operations thread-safe.
- TCP server is minimal (no auth, no persistence protocol versioning)
- Values are strings only
./tests.exe - 7 tests covering basic ops + concurrency
./stress.exe - throws 16 threads at it, checks for data corruption
All passing as of last run.
Built a key-value store in C++ with LRU caching and file-based persistence. Used hash maps for O(1) operations. Implemented crash recovery by replaying an append-only log on startup. Added a TCP server for remote client access over a simple line protocol. Wrote unit tests and benchmarks to validate correctness and measure performance.