upstream app_cluster {
least_conn;
server 10.0.0.10:3000 max_fails=3 fail_timeout=10s;
server 10.0.0.11:3000 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name app.domeniu.ro;
location / {
proxy_pass http://app_cluster;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
}Am pățit-o recent cu un client din e-commerce. A prins o campanie de promovare mai agresivă, traficul a sărit brusc de la 2k la peste 18k utilizatori activi pe zi, iar monolitul lor de pe un singur VPS (8GB RAM, 4 vCPU) a început să dea timeout-uri 504 pe bandă rulantă. Soluția rapidă a clientului? „Avem buget, cumpărăm încă două servere identice, le punem în paralel și am rezolvat problemă”.
Doar că lucrurile nu funcționează așa simplu. Când treci de la un singur nod la o arhitectură distribuită pe 3 servere, aplicația ta trebuie să devină complet stateless. Dacă nu ai mai făcut asta, te lovești direct de trei ziduri: sesiunile userilor, conexiunile la baza de date și fișierele încărcate pe disk.
1. Sesiunile și de ce pică autentificarea fără Redis
Dacă pui un Nginx pe post de Load Balancer care distribuie cererile prin round-robin către Server 1 și Server 2, un utilizator care face login pe Server 1 va fi trimis la următorul click pe Server 2. Fără sesiuni partajate, Server 2 nu știe cine e și îl deloghează instant.
Am văzut mulți dev-i care încearcă să rezolve asta cu ip_hash (sticky sessions) în Nginx. Este un workaround murdar. Dacă Server 1 cade, toți userii alocați pe el își pierd sesiunea și sunt delogați oricum. Soluția curată a fost să mutăm stocarea sesiunilor într-o instanță centralizată de Redis. Nodurile de aplicație devin 100% stateless: nu le mai pasă cine procesează cererea, pentru că ambele citesc token-ul sau session-id-ul din același Redis.
2. Baza de date și limita de conexiuni (PgBouncer)
A doua capcană e conexiunea la Postgres. Când aveam un singur server de aplicație, aveam un connection pool de 20-30 de conexiuni în Node.js. Când pui două sau trei noduri de aplicație, numărul total de conexiuni crește liniar. PostgreSQL gestionează fiecare conexiune ca pe un proces separat în OS, iar pe un server cu RAM limitat, 100+ conexiuni active ucid performanța.
Soluția obligatorie aici este PgBouncer pus în fața bazei de date. Configurat pe pool_mode = transaction, PgBouncer menține 10-15 conexiuni reale deschise către Postgres, dar poate servi sute de clienți virtuali din aplicație. Am economisit cam 40% din memoria bazei de date doar din scăderea overhead-ului de conexiuni, iar latența pe query-uri simple a scăzut de la 120ms la sub 25ms sub load masiv.
3. Upload-urile de fișiere și CDN-ul
Dacă un utilizator încarcă un avatare sau o factură PDF pe Server 1, fișierul ajunge pe disk-ul local din /var/www/uploads. Când alt user dorește să descarce fișierul și nimereste pe Server 2, primește direct 404.
Nu folosiți NFS sau rsync la fiecare 5 minute între servere – aduceți latență și riscuri inutile de sincronizare. Am scos complet sistemul local de fișiere și am trecut totul pe Cloudflare R2 (sau AWS S3) cu un CDN în față. Aplicația generează un presigned URL, browserul urcă fișierul direct în bucket, iar serverele rămân complet libere de stocare locală.
Cum arată arhitectura finală
- Server 1 & Server 2: Aplicație Node.js / Laravel + Nginx local (stataless).
- Server 3: PostgreSQL + PgBouncer + Redis (izolate pe rețea privată).
- Cloud: CDN/S3 pentru assets și un Load Balancer managed de la cloud provider în fața aplicației (costă ~5 euro/lună și e mult mai stabil decât un Nginx configurat de mână pe post de router unic).
Voi ce soluții folosiți pentru sesiuni și load balancing când scalați aplicații monolit? A mers cineva pe soluții cu Keepalived și IP flotant gestionat manual în loc de LB managed?