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:

Algorithms:

Keyingi dars: Monitoring va Observability.