Table of contents
In our last article for rate limiter, we understood the path that we are going to take for building our rate limiter. Today we will look at the implementation of the same.
Lua Script
As discussed, we will employ a Lua script to address potential concurrency issues that might arise when using individual database calls. While Redis transactions (multi & exec) provide a means to achieve atomicity, utilizing a Lua script within Redis offers the advantage of speed. Additionally, the embedded nature of the Lua script allows us the flexibility to design it according to our specific requirements, enhancing both efficiency and customization in managing concurrent operations.
Here is the code that we are going to use.
Explanation
We are mainly playing around 2 keys/user in redis. These are
tokens_<UserId/IP>: This key stores the number of token presently available.
refill_<UserId/IP>: When we refill the keys, we set this key with a TTL of <expiration second>. Next time when we have to generate token, we will be checking if this key exists or not. If it does exist, we are not generating new tokens, if it exists, we generate <tokenLimit-1> tokens, and return true.
So if the token count is greater than 0, we decrease the token count and return 0. If token count is equal to 0, we check if the TTL key is present or not. If present return false, else generate a new token set, reset the TTL for refill_<UserId/IP> and return True.
Express Server
We will be creating an express based api server, and load the Lua script in the redis. I have used node-redis library here, and here's how you load lua from node-redis.
//Read & load rate limiter counter script
const rateLimiterScript = fs.readFileSync('ValidOperationCheck.lua', 'utf8');
const scriptSHA = await redisInstance.scriptLoad(rateLimiterScript);
The above command will return you a SHA for the script you upload. Here is how we can execute the script.
let result = await redisInstance.EVALSHA(scriptSHA,{keys:[req.body.userId]})
Now all we need is to create an api, add a middleware to execute the script, if we get a True we forward the request(next()) or else we return a 429 status.
Here is the complete code for the same
Now this is not a very generic code, but it works correctly. Very soon I will be sharing a more generic version of this project. Stay tuned for more.