System Design
Rate Limiting va Throttling
Rate Limiting — clientdan qancha request qabul qilishni cheklash.
Nega kerak?
1. DDoS Protection
Attacker: 1M requests/sec → Server crash
Rate limit: 100 req/sec → Other 999,900 blocked
2. Fair usage
Client A: 10K req/sec → monopolizes resources
Rate limit: All clients → fair access
3. Cost control
API calls → $$$ (AWS, Stripe, etc.)
Rate limit → budget control
Rate Limiting Algorithms
1. Fixed Window
Window: 1 minute
Limit: 100 requests
00:00 - 00:59 → 100 requests allowed
01:00 - 01:59 → reset, 100 more
const windows = new Map();
function isAllowed(userId) {
const now = Math.floor(Date.now() / 60000); // Current minute
const key = `${userId}:${now}`;
const count = windows.get(key) || 0;
if (count >= 100) return false;
windows.set(key, count + 1);
return true;
}
Burst at boundary:
00:59 → 100 requests
01:00 → 100 requests
→ 200 requests in 1 second!
2. Sliding Window
const requests = []; // Timestamps
function isAllowed(userId) {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove old requests
requests[userId] = (requests[userId] || [])
.filter(t => t > oneMinuteAgo);
if (requests[userId].length >= 100) return false;
requests[userId].push(now);
return true;
}
Accurate
Memory intensive
3. Token Bucket
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity; // 100 tokens
this.tokens = capacity;
this.refillRate = refillRate; // 10 tokens/sec
this.lastRefill = Date.now();
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const tokensToAdd = elapsed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
consume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true;
}
return false;
}
}
Handles bursts
Smooth rate
4. Leaky Bucket
// FIFO queue, fixed processing rate
class LeakyBucket {
constructor(capacity, leakRate) {
this.queue = [];
this.capacity = capacity;
this.leakRate = leakRate; // req/sec
setInterval(() => this.leak(), 1000 / leakRate);
}
add(request) {
if (this.queue.length >= this.capacity) {
return false; // Bucket full
}
this.queue.push(request);
return true;
}
leak() {
if (this.queue.length > 0) {
const req = this.queue.shift();
processRequest(req);
}
}
}
Smooth output
Burst rejection
Redis Implementation
const redis = require('redis');
async function rateLimit(userId, limit = 100, window = 60) {
const key = `ratelimit:${userId}`;
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, window);
}
return count <= limit;
}
HTTP Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1704067200
Retry-After: 60
app.use(async (req, res, next) => {
const allowed = await rateLimit(req.userId);
res.setHeader('X-RateLimit-Limit', 100);
res.setHeader('X-RateLimit-Remaining', remaining);
res.setHeader('X-RateLimit-Reset', resetTime);
if (!allowed) {
return res.status(429).json({ error: 'Too many requests' });
}
next();
});
Distributed Rate Limiting
// Multiple servers share Redis
async function distributedRateLimit(userId) {
const key = `ratelimit:${userId}`;
// Lua script (atomic)
const script = `
local count = redis.call('incr', KEYS[1])
if count == 1 then
redis.call('expire', KEYS[1], ARGV[1])
end
return count
`;
const count = await redis.eval(script, [key], [60]);
return count <= 100;
}
Xulosa
Rate Limiting:
- DDoS protection
- Fair usage
- Cost control
Algorithms:
- Token Bucket - most popular
- Sliding Window - accurate
- Fixed Window - simple
Keyingi dars: Monitoring va Observability.