A naive LRU caching service implemented in C++. It is intended to provide some of the similar functionality as Redis.
-
Major Assumptions and Design Choices:
-
I chose to implement a C++ based caching service because C++ allows customizations on how to access and modify variables (whether it is through pointer or reference or using move syntax, etc.), which is great for building this caching layer because it has a performance requirement. C++ is slightly faster than other languages such as python in general settings.
-
The downside to C++ is that it is not as easy to be integrated with the web application and implementing REST APIs that listen to a network port and return response is nontrivial. Therefore, I simplified the network portion of the API (listening to a port, receiving request and sending response). I made the assumption that all the network requests are represented as C++ function calls here. The header information (which connection the request is coming from) is converted to a string representing the client name (e.g. "client 1", "client 2", "client 3"). The requests/inputs are being received from the
commands
variableinputs.h
, where eachstring
is acommand
. Since the server should be able to concurrently handle multiple requests, each client's connection corresponds to a thread that is scheduled by the server. I used mutex/lock to ensure the atomic property of some of the features. For response/outputs, I chose to ouput it to the ouput file (outputFileName
ininputs.h
), where each input command is followed by the response ouput in the next line. Under this assumption, I could implement the functionality and the service could be wrapped with a network API that supports easy ways to listen to requests and sending response. -
I assume that the cache has a size limit (which could be customized under inputs.h). The size of the input and output and the cache should not overflow the machine's memory.
-
I assume that all values are
string
(since this is also how redis backend is working). -
I assume I do not have cover the case to recover from a fatal cache server error. The only interrupt from other clients happen when one client thread is switched out of CPU and other client get swithed into the same CPU. I use lock to ensure the writing operations and transaction operations are atmoic to avoid race condition.
-
The design: business logic /
main.cpp
--> service logic /service.cpp
--> caching datastore /cache.cpp
.main.cpp
serves as the main entry of the program. All the detailed logics are separated inmain.h
to make it more readable and maintainable. The main business logic, the service logic and the caching layer are separated to make them independent of each other so that when we change one of them (for exmaple, change to a different eviction policy), we don't have to change other layers of the program. -
Cache / Perfomance: I chose to implement a LRU cache with a doubly linked list and a hash map, which ensures constant O(1) insertion and lookup while keeping the cache under its capacity. I pass the
string
orvector
by reference to avoid unnecessary copy. I usereserve
to avoid dynamic allocation osstring
orvector
in some cases.
-
-
How to setup? Please compile it in a c++ runnable environment and use g++/gcc compiler. If the operating system supports Makefile, compile by typing
make all
. If the above step does not work, please compile the following files separately by typing:g++ -g -std=c++11 -c cache.cpp -o cache.o
g++ -g -std=c++11 -c helpers.cpp -o helpers.o
g++ -g -std=c++11 -c service.cpp -o service.o
g++ -g -std=c++11 -o main main.cpp helpers.o service.o cache.o
After compiling, you could change any of the inputs in the
inputs.h
and runmain
or./main
, depending the operating system that you have. -
Supported features:
SET
andGET
(assume there are no expiration request, and does not support additional flags such asNX
andXX
because of the time limit but they could be easily extended based on the current program). Examples:SET key1 value1
GET key1
MGET
andMSET
(assumingMSET
never fails and always upserts the data. In the implementaion just reuse the above functions). Examples:MSET key1 1 key2 2 key3 3 key4 4 key5 5 key6 6
MGET key1 key2 key3 key4 key5 key6 key7
- A cache eviction policy based on Least Recently Used Cache.
INCR
andDECR
(assuming we allow overflow (INT_MAX + 1 = INT_MIN is acceptable) and integer is int32).MULTI
andEXEC
inTransaction
.