Segment Redis Proxy is a service which adds read-through LRU caching on top of a single Redis backing instance.
The service supports only the GET command using either HTTP or RESP to communicate with clients.
Once you set the environment variable, PROTOCOL, to HTTP you can send a GET request from to the proxy (assuming the gabriel key is set):
curl -X GET http://localhost:8080/gabriel
Similarly, once you set the environment variable, PROTOCOL, to RESP you can connect to the proxy using redis-cli (assuming the gabriel key is set):
$ redis-cli -p 8080
127.0.0.1:8080> GET gabriel
"samson"
127.0.0.1:8080> SET gabe samson
(error) ERR unsupported command 'SET'
127.0.0.1:8080>
- Proxy - The proxy component is responsible for handling GET requests using read-through mechanisms - tying together the cache and Redis components. The proxy has two variations: the HTTP proxy and the RESP proxy. These two variations differ by communication protocol but still have the same functionality. At start time, the proxy variation is selected based on the coniguration.
- Cache - The cache component is a fixed-size thread safe LRU cache with a global time-to-live. This implementation is based on the groupcache package.
- Redis - The Redis component is exactly that, Redis. This also includes the client packages needed to communicate with the backing instance.
Environment Variables:
PORT: The port the proxy should listen on. (default:8080)PROTOCOL: The communication protocol to use when listening to clients. One of:RESPorHTTP. (default:HTTP)REDIS_ADDR: The server address of the backing Redis instance. (default:localhost:8080)CACHE_CAPACITY: Maximum number of items the cache should retain. (default:100)CACHE_TTL: Maximum number of seconds an item should be retained in the cache before being evicted. (default:300)
The following make targets are provided: deps, format, lint, test, and container-image.
You can run the service locally using Docker Compose:
$ docker-compose up
You can run the service tests using:
$ make test
Testing requires a redis instance running on the address localhost:6379. The redis service in docker compose automatically bind itself to port 6379.`
To build a production-ready image use:
$ make container-image
Container images are named gpsamson/segment-redis-proxy by default, and they are tagged with the hash of the current commit. You can override these using IMAGE and TAG environment variables.
Cache operation complexities:
Get()- time complexity ofO(1)Set()- time complexity ofO(1)
- Research and Design (~1hr)
- Cache (~1.5hr)
- HTTP Proxy (~1hr)
- TCP Proxy (~1.5hr)
- Documentation, build files, etc. (~30mins)
The service meets all requirements except for the "sequential concurrent processing" requirement. Nonetheless, Go's built-in packages already ship with great concurrency support so the bonus "parallel concurrent processing" requirement was prioritized. For a service like this parallel requests are favorable.
- Specific TODOs are scattered throughout the code. 👀
- I decided to roll out a custom cache package because using any open-source package would require too many modifications to meet the caching requirements. It also seemed more appropriate since this is a coding excercise.
- Instead of having the server handlers implement the read-through mechanisms we can
create a
RTCachethat takes care of it - passing this as the cache dependency. It's almost like a wrapper around the LRU cache. - If we wanted the LRU cache to evict items as their TTL comes to an end we could create a goroutine to periodically remove expired items. I'm not sure if this would hog the mutex too often and affect others.