This post was originally posted on my company's blog here.
I recently wanted to implement a rate limiter in typescript for my site Opinly and seeing as we use SST to deploy on AWS, I took a stab at writing one up myself using DynamoDB. Needless to say (considering I'm blogging about it) I'm pretty happy with how it came out.
There was the option of using Redis on Upstash, but I wasn't keen to bring in a new third party dependency if I didn't have to.
I've been using DynamoDB for a while and seeing as its scale to zero serverless and decently low latency I thought it would be a great choice.
I managed to make the whole rate limiter work with only one DynamoDB hit per request, so I've been seeing added latency in the low double digit milliseconds which is acceptable for my use case.
The Code
Firstly, some things to note regarding setting this up yourself.
Make sure that your Partition Key, Sort Key, TTL attribute, and Table Name are changed to match your setup.
This is how my infrastructure as code looks with SST, but its just as easy with CDK or the console
Moving on to the fun stuff, the real meat of the functionality comes from the UpdateCommand call made to dynamo db
What we're doing here is calculating the time period for the counter that we're going to be incrementing.
Once that is calculated we attempt to increment the count, but we have a condition on the call to ensure that the limit in the database is less than or at the limit we've set in the parameter.
If the limit is below our rate, then we return false. If not then we will fall into the catch block.
DynamoDB will throw a specific Error class called ConditionalCheckFailedException only when the condition specified did not match. In our case its
"attribute_not_exists(#rateLimitCount) OR #rateLimitCount <= :limit"
If it is that specific type of error then we either throw the error specified or return true depending on the parameters.
In the rare case that dynamo throws a different error then we throw that and let the next guy deal with it.
Conclusions
I like the implementation of this rate limiter as its:
- Very simple
- Doesn't require any third party services
- Can be extended for more functionality such as blocking a user after too many exceeded attempts if needed.
- Low latency - As I mentioned the single request varies between 5-50ms but this could be improved if you provisioned DynamoDB with the necessary Write Capacity. I just have mine set to pay per request.
- Scalable - Can scale as far as Dynamo can, which is the whole point of the database
- Cheap - We're using 1 RCU per request, and the TTL means that the counters will delete themselves when needed
Top comments (0)