import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
// Creăm instanța folosind algoritmul Sliding Window
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 request-uri la 10 secunde
analytics: true,
prefix: "@upstash/ratelimit",
});
export async function middleware(request: Request) {
// Identificăm userul după IP sau API Key
const identifier = request.headers.get("x-api-key") ?? "ip_anonim";
const { success, limit, reset, remaining } = await ratelimit.limit(identifier);
if (!success) {
return new Response("Prea multe cereri. Încearcă mai târziu.", {
status: 429,
headers: {
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
},
});
}
}Am picat un API anul trecut la un client de e-commerce pentru că cineva s-a trezit să facă scraping agresiv pe prețuri direct pe un endpoint neprotejat. În două minute, serverul de Node era în genunchi, cu event loop-ul blocat la 99% CPU. Am rezolvat problema repede, dar am ales greșit prima unealtă de rate limiting și am pierdut câteva ore bune înțelegând de ce regulile mele nu funcționau cum trebuie.
Dacă construiești API-uri, rate limiting-ul nu e opțional. Întrebarea e doar unde îl pui și ce trade-off-uri îți asumi.
Nginx: Protecție brută la nivel de IP
Nginx e primul scut. Când vrei să oprești atacurile stupide de tip brute-force sau botii necomplexați direct la intrare, limit_req_zone din Nginx e sfânt. Rulează în spatele unui C/C++ optimizat și blochează request-ul înainte ca el să atingă măcar runtime-ul tău de aplicație (Node, Go, Python).
Unde e genial: Blochează traficul nedorit cu un cost de memorie și CPU practic nul. Procesează lejer zeci de mii de request-uri pe secundă.
Trade-off-ul sincer: Nginx identifică userii în principal după IP ($binary_remote_addr). Dacă ai 50 de oameni dintr-un birou care ies pe internet prin același IP NAT, s-ar putea ca unul singur să-ți declanșeze limita și să le blochezi tuturor colegilor accesul. În plus, dacă vrei reguli dinamice per user_id extrase dintr-un JWT, Nginx devine greoi și necesită module complexe de Lua (OpenResty).
In-Memory (Node/Go process): Bun doar pentru monolith de test
Cea mai simplă soluție din cod este o librărie de genul express-rate-limit care ține un Map sau un LRU cache în memoria RAM a procesului. Am folosit asta la început pe un proiect mic.
Unde e genial: Latency 0ms. Nu faci niciun network call extern pentru a verifica starea limitelor.
Buba: Nu scalează. Când am mutat aplicația de la o singură instanță la un cluster cu 4 containere Docker în spatele unui Load Balancer, o limită de 100 de request-uri/minut a devenit de fapt o limită de 400 de request-uri/minut. Fiecărui container îi păsa doar de memoria lui locală. Iar dacă folosești arhitecturi serverless (AWS Lambda, Vercel), in-memory rate limiting e complet inutil, pentru că starea dispare între invocări.
Redis și Upstash: Calea corectă pentru API-uri moderne
Dacă ai un sistem distribuit, microservicii sau arhitectură serverless, starea trebuie stocată într-un cache centralizat. Aici intervine Redis.
La un API cu aproximativ 4 milioane de apeluri pe zi, am trecut complet pe @upstash/ratelimit. Upstash este practic un Redis managed peste HTTP, creat special pentru serverless. Un Redis clasic te obligă să menții conexiuni TCP persistente, ceea ce crapă rapid la cold-starts în Lambda/Vercel.
Câștigul masiv la Redis/Upstash este că poți folosi algoritmi avansați ca Sliding Window (nu doar Fixed Window, care are problema de spike la marginea intervalului) și poți pune chei dinamice: rate_limit:${userId} sau rate_limit:${apiKey}.
Trade-off-ul sincer: Adaugi network latency. Fiecare request procesat va face un round-trip la Redis. Dacă Redis-ul tău e găzduit în altă regiune sau cloud decât serverul de aplicație, adaugi 20-50ms latență la fiecare call! Soluția e să ții Redis-ul în aceeași regiune cu aplicația (under 2ms latency) sau să folosești un mecanism de epoll/multi-region caching.
Cum le combin eu în producție
De obicei folosesc o abordare hibridă:
- La nivel de Nginx/Cloudflare: Limite de siguranță agresive pe IP (ex: max 300 req/min per IP) pentru a opri atacurile brute.
- La nivel de Aplicație (Upstash / Redis): Limite de business inteligente per API Key sau User ID (ex: 10 req/min pe tier-ul moca, 1000 req/min pe tier-ul Pro).
Voi cum gestionați rate limiting-ul în proiectele voastre? Ați rămas pe reguli simple la nivel de Reverse Proxy sau ați mutat logica în cod?