Redis Cache Locking
8 January 2025
This blog post has been due for half an year. The first POCs were done around August last year. I just couldn't find time to write it, so busy with a lot of projects and even personal problems.
After the first time I implemented Cloudflare HTTP caching, a problem I discovered was cache stempede. It started happening when cache filling required for time than the cache validity. While working on a new project Noonu Atoll Council Public Engagement Platform, which uses an external API, Microsoft Graph API to fetch data. So I wanted to implement a better caching strategy to avoid cache stempede.
Fortunately Redis was the perfect solution for this. I decided to use pub/sub to handle cache locking. So I wrote a well optimized adapter to call Microsoft Graph API, and handle caching of responses. I didnt just use KV, used hashes too, to store lists of items. Used redis NX to create a lock, then used pub/sub to allow waiting for lock and provide data to the waiting requests. Great how javascript itself made this also relatively simple to accomplish, just learnt to create custom promises. Now the app can comfortably use Microsoft Graph API while limitting its usage to the minimum, and also avoid getting rate limited. Website will be blazingly fast and reliable.
Basic steps are as follows:
- Check if a fresh value exists in cache
- If a fresh value exists return it
- Otherwise, try to aquire the cache lock
- If cache lock is aquired, do the api call, update the cache, send a pub/sub about it, return the data
- Otherwise, wait for a pub/sub, and return data from it
- If waiting for pub/sub times out, return whatever is in the cache
- If cache doesnt exist, do an api call and return it, and also fill the cache
Lets not just finish with Redis only cahcing. The cache locking method can
also be done in Nginx with a few configuration options of proxy_cache_lock.
And it also uses Remix's defer to stream data as soon as it is available.
proxy_cache_path /var/cache/nginx keys_zone=my_cache:20m
use_temp_path=off;
proxy_cache my_cache;
proxy_cache_lock on;
proxy_cache_lock_timeout 20s;
- Remix
- Redis
- Nginx
- Cache