An extremely simplified key/value store that sorta kinda implements some of the Redis RESP spec that I hacked together in a few hours for a job interview. Currently supports the following commands:
- GET
- DEL
- SET
Note:
- Keys and values are limited to 4096 bytes.
- Only supports basic strings in the form
$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
.
- Implement TCP listener to handle incoming client requests.
- Create parser / decoder to process incoming commands.
- Create thread-safe key-value store to hold data.
- Return Redis Wire protocol encoded responses.
- List command.
- Data store abstraction.
- Automatic key expiration.
- Persistence - save data to disk, read in contents during startup.
- Partitioning - would require a load balancer / router.
-
Redis can store up to 512 MB per key. Obviously allocating this much data per request would be terribly inefficient, especially for requests that are only a few bytes in size. Reading incoming request data can be done using chunking where we read in say 1024 KB at a time, writing the data to a byte buffer.
- Note: I ended up opting for a basic solution for now due to issues reading CRLF requests correctly. Would like to re-address this down the road.
-
Manually parsing the command bytes, but will probably refactor to use simple regex checks.
There's a tremendous amount of stuff I would like to, and would have liked to do for this project, such as:
- Passing a context to cancel requests.
- Redo how I did the parsing entirely (Regex would have been simpler / cleaner).
- Adding a graceful shutdown by monitoring sigint and having the network later update a channel whenever a request as in-flight.
- TCP code is not great. I got hung up trying to get bufio/bytes package, etc. working with conn.Read. I suspect the issue with the CRLF, but I decided to move after spending a bit too long on it. However, this left me having to use a pre-allocated byte slice of 4096 which is of course, much less than ideal :(
- Package layout isn't great. Naming is hard. This would definitely be part of a refactor.
- The storage system uses a standadrd
RWMutex
vs async.Map
sincesync.Map
is more optimized for high reads and fewer rights. - I have not written as many tests and or bench marked everything as well as I'd have liked. I also haven't ran the race detector.
Jason Welch (jasonw.developer@gmail.com)