Skip to content

Commit 53eefc2

Browse files
readme updated
1 parent 47f2697 commit 53eefc2

10 files changed

Lines changed: 1065 additions & 27 deletions

.postman/config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"workspace": {
3+
"id": "c5e7a51b-c99f-4b39-813c-19f83105cae8"
4+
},
5+
"entities": {
6+
"collections": [],
7+
"environments": [],
8+
"specs": [],
9+
"flows": [],
10+
"globals": []
11+
}
12+
}

README.md

10.6 KB

rate-limiter

docker compose up --build

🚀 Rate Limiter API (Microservice)

A high-performance, Dockerized rate limiting microservice built with Node.js, TypeScript, Redis, Lua scripting, and Redis Cluster, implementing multiple real-world algorithms used in API Gateways.


📑 Table of Contents


⭐ Features

✔ Implements four industry-standard algorithms:

  • Fixed Window
  • Sliding Window
  • Token Bucket
  • Leaky Bucket

Redis Cluster support for high scalability
Atomic operations using Lua
/metrics endpoint aggregates: allowedRequests, blockedRequests, tokensRemaining, resetTime, totalRequests
✔ Dockerized microservice
Basic Auth (for testing)
✔ Modular folder structure

🔮 Future Additions

  • Sliding Window Log algorithm
  • Load balancing layer
  • API Keys & RBAC (Redis-only possible)
  • Middleware-level caching
  • Gateway features:
    • rate limiting
    • authentication
    • load balancing
    • circuit breaking
  • Prometheus/Grafana dashboards
  • Distributed tracing (OpenTelemetry)

🛠 Tech Stack

Node.js • TypeScript • Express.js • Redis / Redis Cluster • Lua • Zod • Docker & Docker Compose


🧩 Architecture Overview

Client → API Gateway (future) → Rate Limiter Service → Redis / Redis Cluster

markdown Copy code

  • Each algorithm uses:
    • Dedicated Redis keys
    • Atomic Lua scripts
    • Isolated logic for dashboard comparison

🧠 Rate Limiting Algorithms (Full Details)

1️⃣ Fixed Window Algorithm

The Fixed Window algorithm assigns a fixed number of allowed requests inside a fixed time window.

✔ How it Works

  • Example: 10 requests per 60 seconds
  • Requests exceeding limit → blocked
  • Counter resets when next window starts

❌ Major Issue — Burst Problem

  • 10 requests at 59th sec + 10 at 1st sec of next window → 20 requests in 2 sec → possible overload
  • Reason: tracks only current window, not the last 60 seconds
2️⃣ Token Bucket Algorithm

Stores requests as tokens in a bucket.

✔ How it Works

  • Bucket has fixed capacity
  • Tokens refill at a fixed rate
  • Each request consumes 1 token
  • If tokens exist → request allowed, else blocked

✔ Burst Handling

  • Supports bursts up to bucket capacity
  • Smooth traffic control

✔ Example

  • Capacity = 10 tokens, Refill = 1 token/sec
  • 10 requests → allowed
  • 11th → blocked
  • After 1 sec → 1 token refills → allowed
3️⃣ Leaky Bucket Algorithm

Ensures constant output rate.

✔ How it Works

  • Requests enter a queue (bucket)
  • Processed at fixed leak rate
  • If bucket full → request rejected

✔ Characteristics

  • Smooth & uniform traffic
  • Prevents burst attacks
  • Protects server load

❌ Limitation

  • No bursts allowed
  • Example: Leak rate = 5 req/sec → 100 requests arrive → only 5 processed/sec, rest queued/rejected
4️⃣ Sliding Window Algorithm

Improves Fixed Window by tracking requests in the last N seconds, not fixed blocks.

✔ How it Works

  • Fairer distribution
  • Prevents burst issues at window edges

🔥 API Endpoints

POST /api/limiter/test → Fixed Window POST /api/limiter/sliding → Sliding Window POST /api/limiter/tokenbucket → Token Bucket POST /api/limiter/leakybucket → Leaky Bucket POST /api/limiter/all → Run all algorithms together

yaml Copy code


📦 Example Response

{
  "activeKeys": 1,
  "response": { ... },
  "blockedRequests": 27,
  "allowedRequests": 67,
  "totalRequests": 94
}
🐳 Setup & Installation
bash
Copy code
git clone <repo-url>
docker compose up --build
Starts: Redis Cluster, Node server, Lua scripts

🧪 Testing & Load Scenarios
Test bursts, allowed vs blocked, algorithm comparison via /metrics

Tools: Postman Runner, k6, Artillery, JMeter

📬 Postman Collection
[ Postman collection to test all endpoints:](https://app.getpostman.com/join-team?invite_code=13841166f45640e9b8c8e3a36a9cfc49afed7ac4d4cf1a0be867b7b82d1d0ed8&target_code=bc4d4f0c1c320a256972bc868580defb)

Open Postman Collection

🧭 Roadmap
Sliding Window Log algorithm

API Keys & RBAC (Redis-only)

Middleware caching

Load balancing

Circuit breaking

Request transformation

Prometheus + Grafana dashboards

Distributed tracing (OpenTelemetry)

📄 License
MIT License — free for personal & commercial use.

backend/src/algo-lua/slidingWindow.lua

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,61 @@ local statsKey = KEYS[2]
55
local globalStats = KEYS[3]
66

77
local limit = tonumber(ARGV[1])
8-
local windowSize = tonumber(ARGV[2])
9-
local tokenRequested = tonumber(ARGV[3])
10-
local currentTime = tonumber(ARGV[4])
11-
8+
local windowSize = tonumber(ARGV[2]) -- in ms
9+
local tokensRequested = tonumber(ARGV[3])
10+
local currentTime = tonumber(ARGV[4]) -- in ms
1211

12+
-- Window boundary
1313
local windowStart = currentTime - windowSize
14+
15+
-- Remove old timestamps
1416
redis.call("ZREMRANGEBYSCORE", redisKey, 0, windowStart)
15-
local count = tonumber(redis.call("ZCARD", redisKey)) or 0
16-
local newCount = count + tokenRequested
1717

18+
-- Current request count
19+
local count = tonumber(redis.call("ZCARD", redisKey)) or 0
20+
local newCount = count + tokensRequested
1821

22+
-- If limit exceeded → block
1923
if newCount > limit then
20-
local blocked= redis.call("HINCRBY", statsKey, "blocked", 1)
21-
local total= redis.call("HINCRBY", statsKey, "total", 1)
24+
local blocked = redis.call("HINCRBY", statsKey, "blocked", 1)
25+
local total = redis.call("HINCRBY", statsKey, "total", 1)
26+
2227
redis.call("HINCRBY", globalStats, "blocked", 1)
2328
redis.call("HINCRBY", globalStats, "total", 1)
29+
2430
return {
25-
0,
26-
limit - count,
27-
windowStart + windowSize - currentTime,
28-
windowStart + windowSize,
29-
blocked,
30-
total,
31-
total - blocked
31+
0, -- blocked
32+
limit - count, -- remaining
33+
(windowStart + windowSize) - currentTime, -- retry after
34+
windowStart + windowSize, -- window reset time
35+
blocked, -- blocked count
36+
total, -- total count
37+
tonumber(redis.call("HGET", statsKey, "allowed")) or 0
3238
}
33-
3439
end
35-
36-
redis.call("ZADD", redisKey, currentTime, tostring(currentTime) .. "-" .. tostring(math.random()))
40+
41+
-- Allow the request
42+
-- Only store timestamp entries
43+
local id = tostring(currentTime) .. "-" .. tostring(math.random())
44+
redis.call("ZADD", redisKey, currentTime, id)
45+
46+
-- Expire key after window
47+
redis.call("EXPIRE", redisKey, math.floor(windowSize/1000) + 1)
3748

3849
local allowed = redis.call("HINCRBY", statsKey, "allowed", 1)
39-
local total= redis.call("HINCRBY", statsKey, "total", 1)
50+
local total = redis.call("HINCRBY", statsKey, "total", 1)
51+
4052
redis.call("HINCRBY", globalStats, "allowed", 1)
4153
redis.call("HINCRBY", globalStats, "total", 1)
4254

4355
return {
44-
1,
45-
limit - newCount,
46-
windowStart + windowSize - currentTime,
47-
windowStart + windowSize,
48-
allowed,
49-
total,
50-
total - allowed
51-
}
5256

57+
1, -- allowed
58+
limit - newCount, -- remaining
59+
(windowStart + windowSize) - currentTime, -- retry after
60+
windowStart + windowSize, -- window reset time
61+
allowed, -- allowed count
62+
total, -- total count
63+
tonumber(redis.call("HGET", statsKey, "blocked")) or 0
5364

65+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"info": {
3+
"_postman_id": "09c1580a-e498-4bc3-8eb1-69cdb91d38a7",
4+
"name": "New Collection",
5+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6+
},
7+
"item": []
8+
}

0 commit comments

Comments
 (0)