Database
Building a rate limiter, why even use Redis.
Tejaswa Hinduja DEV Community
2 views
Well I failed a amazon OA because of a rate limiting question, So I have built this rate limiter from first principles and haven't followed any tutorial. Through this blog, I want to answer questions like: Why even use Redis to make a rate limiter? Why not just use a Node.js process?
What are the issues with using Node.js?
Node.js race conditions: If we use an in-memory map, async execution causes race conditions. If we use another DB to store the rate-limiting data, it can still cause race conditions because most DBs do not block queries from reading data even if another query hasn't finished writing the new state.
Millisecond collisions: Two requests hitting at the same millisecond could read and write into the same timestamps, causing a rate-limit bypass.
Data structure choices: Why use a ZSET instead of a simple map with keys and an array?
Library flaws: How packages like express-rate-limit handle some of these flaws, but still suffer from a "boundary burst" problem because they use a fixed-window approach. Also I didnt want to use a npm package and wanted to build this from scratch.
Node.js Event-Loop Race Conditions
Node.js is single-threaded, but it is asynchronous. When execution hits an await (like fetching user context, parsing a JWT, or logging), Node pauses that function execution and yields control back to the Event Loop.
If two requests from the same IP hit your server within the same millisecond:
Request A reads timestamps (length = 4).
Request A hits an await boundary.
The Event Loop pauses Request A and starts processing Request B.
Request B reads the exact same timestamps array in memory (still length = 4!).
Both requests pass the length >= 5 check, both write their timestamps, and the rate limit is bypassed.
If Node Memory Fails, Why Not Use a Standard Database (MongoDB/SQL)?
If in-memory maps fail, the logical next step is persisting timestamps in a database. However, this shifts the concurrency bug from Node.js to the Database engine.
Most traditional databases do not queue incoming queries into a single-file line—doing so would tank read throughput. Instead, they use Multi-Version Concurrency Control (MVCC).
Standard databases allow stale reads while writes are still in flight. Unless you apply explicit, heavy row locks (SELECT FOR UPDATE), Request B reads the uncommitted state before Request A's write finishes processing. The database isn't broken—it's performing as designed, but its isolation model fails read-check-write validation without strict locks.
The express-rate-limit Fixed Window Flaw (Boundary Burst)
Packages like express-rate-limit fix basic in-memory issues when backed by a store, but by default, they rely on a fixed-window algorithm.
This leads to the Boundary Burst problem: if your limit is 100 requests per minute resetting at 12:00, an attacker can send 100 requests at 12:00:59 and another 100 requests at 12:01:01. The counter resets at 12:01:00, allowing 200 requests within a 2-second window while technically staying under the limit.
Why Redis Uses a ZSET (Map + Skip List) Instead of a Map + Array
A sorted array lets us search for the window cutoff in O(logN) time using binary search. However, rate limiters are write-heavy engines:
Insertion & Pruning in Arrays: Inserting or removing timestamps forces the CPU to shift surrounding elements in RAM—an O(N) memory-copy operation (memmove).
Redis solves this by combining two data structures into a ZSET:
Hash Map (dict): Maps Value ──> Score. Gives O(1) time complexity to look up or check if a member exists.
Skip List (zskiplist): Maintains elements ordered by Score.
By trading a tiny bit of extra memory (≈0.33N extra pointers) to build hierarchical Express Lanes over a standard linked list, Skip Lists achieve O(logN) search speeds while retaining O(1) pointer-swap insertions and deletions.
The structure is like this
Check out the code :- https://gist.github.com/TejaswaHinduja/48ecaf4a63d29306bd255456588fb5c4
If you liked the blog do leave a like and connect with me on socials:
LinkedIn:https://www.linkedin.com/in/tejaswahinduja/
Twitter:https://x.com/Tej_Codes
Github:https://github.com/TejaswaHinduja
Read original: https://dev.to/tejaswahinduja/building-a-rate-limiter-why-even-use-redis-20b4
← Previous
Parse crypto amounts like hostile input, declare rounding first
Next →
One Python Agent Core, Four Ways to Run It: Nova on Server, Web, TUI, and Desktop
Related
Backyard Endurance OS: Designing Zero-Loss Telemetry Ingestion for Athletes and Distributed Systems
Database
4
DEV Community
Stop Trusting the App: Enforcing Append-Only at the Database Layer
Database
6
DEV Community
Nexus Core v1.7.0: Moving from MongoDB-Centric to Multi-Database Architecture
Database
9
Dev.to (EN Zone)
The bug where every check passed and the data was still wrong
Database
8
DEV Community
Comments0
No comments yet — be the first