eduardweb.
PM2 & NodeIntermediar#nodejs#devops#pm2#javascript

Cum configurezi corect PM2 în producție: cluster, zero-downtime și OOM

De Mihai Popescu, 4 sept. 2026 · 14 vizualizări · 2 like-uri

Postat 4 sept. 2026
javascript
module.exports = {
  apps: [
    {
      name: 'api-service',
      script: './dist/server.js',
      instances: -1,
      exec_mode: 'cluster',
      kill_timeout: 5000,
      wait_ready: true,
      listen_timeout: 8000,
      max_memory_restart: '800M',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000
      }
    }
  ]
};

Multă lume pornește Node.js în producție cu un simplu pm2 start index.js -i max rulat direct pe server și speră să țină. Am făcut și eu prostia asta acum vreo 7 ani, până când am bușit un magazin online chiar în timpul unui flash sale pentru că deploy-ul dădea drop la conexiuni active.

Dacă vrei stabilitate pe un VPS fără orchestrare complexă de Kubernetes, totul trebuie definit într-un fișier ecosystem.config.js versionat în Git.

Cluster mode fără iluzii

Node.js e single-threaded pe event loop, deci pe o mașină cu 4 nuclee vrei 4 procese. Opțiunea instances: 'max' sună tentant, dar în realitate prefer să las mereu un core liber pentru sistemul de operare, Nginx sau instanța locală de Redis. Dacă serverul are 4 vCPU-uri, pun instances: 3 sau instances: -1.

Trade-off-ul evident: dacă ții sesiuni sau cache direct în memoria procesului (global.cache = {}), cluster mode o să-ți rupă aplicația. Fiecare worker are propriul heap. Dacă ai nevoie de state partajat sau WebSockets (Socket.io), treci pe Redis Adapter înainte să activezi clusterul, altfel utilizatorii vor primi date inconsistente între request-uri consecutive.

Graceful reload: adio 502 la deploy

Când rulezi pm2 reload all, PM2 trimite semnalul SIGINT workerilor, așteaptă să se oprească și pornește procesele noi pe rând. Problema apare dacă aplicația ta nu știe să asculte semnalul ăsta.

Implicit, PM2 presupune că noul worker e gata în secunda în care procesul a fost spawnat. Dacă inițializarea bazei de date durează 3 secunde, Nginx va trimite trafic către un worker mort, iar clienții vor vedea erori 502 Bad Gateway.

Soluția cere două lucruri în ecosystem.config.js:

  1. wait_ready: true — PM2 nu rutează trafic până când aplicația nu rulează process.send('ready').
  2. listen_timeout: 8000 — cât așteaptă înainte să considere workerul eșuat.

În codul Node, apelezi process.send('ready') abia după ce server.listen() a terminat și conexiunile la Postgres sau Mongo sunt deschise. Când vine SIGINT, închizi serverul HTTP cu server.close(), permiți conexiunilor în curs să se termine și apoi faci process.exit(0).

Salvarea de la OOM (Out Of Memory)

La un proiect cu vreo 12k useri zilnici am avut un memory leak obscur într-un PDF generator. Consuma cam 15MB pe oră. Pe un VPS de 4GB, după trei zile Linux OOM killer intra pe fir și omora direct procesul principal fără niciun avertisment, uneori lăsând baza de date blocată.

Directiva max_memory_restart: '800M' rezolvă problema asta pragmatic. Dacă un worker depășește pragul, PM2 îl restartează elegant în fundal fără să afecteze ceilalți workeri din cluster. Nu rezolvă bug-ul de memorie din cod — pe care tot a trebuit să-l găsim cu heap snapshots —, dar transformă un downtime catastrofal de noapte într-un non-eveniment.

Atenție la loguri

PM2 scrie out.log și error.log până umple discul. Dacă nu instalezi modulul de logrotate, te trezești într-o vineri seară cu 100% disk usage și serverul blocat complet.

După ce ai setat fișierul de configurare, rulează obligatoriu:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 10

Voi cum gestionați reload-ul proceselor Node în CI/CD, lăsați PM2 să facă rolling update sau preferați blue/green direct din Nginx?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.