module.exports = {
apps: [{
name: 'my-api',
script: './dist/server.js',
instances: 'max',
exec_mode: 'cluster',
wait_ready: true,
listen_timeout: 5000,
kill_timeout: 3000,
max_memory_restart: '500M',
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
env: {
NODE_ENV: 'production'
}
}]
};Am văzut prea mulți devi care rulează Node în producție cu un simplu și optimist pm2 start app.js. Fără cluster mode, fără log rotation, doar speranță și rugăciuni. Am pățit-o și eu acum câțiva ani, când un memory leak subtil mi-a trântit un API fix în timp ce eram la o bere în oraș.
De atunci, nu mai plec la drum fără un fișier ecosystem.config.js bine pus la punct. Chestia asta îți transformă procesul Node dintr-un script fragil într-un serviciu serios de producție.
Cluster mode și mitul celor 0 milisecunde de downtime
Dacă ai un server cu mai multe nuclee, e păcat să rulezi o singură instanță de Node. Node.js este single-threaded, așa că restul nucleelor stau degeaba în timp ce procesul tău principal gâfâie sub asaltul request-urilor.
La un proiect cu vreo 12.000 de useri activi, trecerea la instances: 'max' și exec_mode: 'cluster' ne-a scăzut încărcarea pe procesor cu aproape 30%. În plus, primești graceful reload aproape moca. Când dai pm2 reload all, PM2 restartează instanțele pe rând, una câte una. Utilizatorii tăi nu vor vedea nicio secundă de downtime în timpul deploy-ului.
Trade-off-ul sincer: Cluster mode e genial, dar îți rupe aplicația dacă ții sesiunile utilizatorilor în memoria RAM a procesului (in-memory state). Dacă instanța 1 are sesiunea userului și următorul request ajunge la instanța 2, userul se trezește delogat. Ai nevoie de Redis pentru sesiuni și de o arhitectură stateless. Altfel, mai rău încurci lucrurile.
Auto-restart la OOM (Out of Memory)
Node are prostul obicei să mănânce memorie dacă ai un leak de care n-ai dat încă în faza de testare. Decât să lași procesul să mănânce tot RAM-ul de pe VPS și să blocheze sistemul de operare, mai bine folosești max_memory_restart.
Eu pun de obicei o limită safe de 400M sau 1G, în funcție de cât RAM are serverul. Dacă procesul trece de limita asta, PM2 îi dă un kill curat și îl repornește automat. Nu e o soluție elegantă pentru memory leak-uri, dar măcar îți ține aplicația în picioare până apuci să faci debugging.
Log-urile care îți mănâncă tot SSD-ul
Am pățit pe un server mic de test să mă trezesc cu eroarea fatală 'No space left on device'. Vinovatul? Un fișier de loguri de 45GB generat de PM2 în doar trei luni de zile din cauza unor mesaje de debug uitate active.
PM2 nu știe nativ să facă log rotation fără un modul extern. De aceea, pe orice server nou instalez rapid pm2-logrotate din terminal și configurez formatul datei direct în fișierul de configurare. Separarea logurilor de eroare de cele de acces este sfântă.
Pentru un graceful reload cu adevărat curat, ai grijă să asculți de semnalul SIGINT în codul tău Node ca să închizi conexiunile la baza de date înainte ca PM2 să omoare procesul de tot. Altfel, te trezești cu conexiuni 'agățate' în PostgreSQL sau MongoDB.
Voi cum gestionați logurile în producție? Le lăsați local pe server cu logrotate sau le trimiteți direct într-un stack extern de tip ELK sau Datadog?