const express = require('express');
const router = express.Router();
let eventLoopLag = 0;
setInterval(() => {
const start = Date.now();
setImmediate(() => {
eventLoopLag = Date.now() - start;
});
}, 1000).unref(); // unref ca să nu țină procesul deschis aiurea
router.get('/health', async (req, res) => {
// 1. Verificăm event loop lag-ul
if (eventLoopLag > 200) {
return res.status(503).json({ status: 'unhealthy', reason: `Lag prea mare: ${eventLoopLag}ms` });
}
// 2. Verificăm conexiunea la DB (exemplu generic)
try {
// await db.ping();
} catch (err) {
return res.status(503).json({ status: 'unhealthy', reason: 'Database connection failed' });
}
res.status(200).json({ status: 'healthy', lag: `${eventLoopLag}ms` });
});
module.exports = router;Să fim sinceri: comanda pm2 status este cel mai mare mincinos din toolchain-ul nostru de Node.js. Mi s-a întâmplat de zeci de ori în ultimii ani ca PM2 să raporteze verde frumos, status "online", în timp ce aplicația mea era complet moartă în spate, blocată într-un loop infinit sau cu conexiunea la baza de date pierdută.
Dacă te bazezi doar pe procesul de sistem, o să afli că producția e jos direct de la clienți, nu de la alerte. Hai să vedem de ce PM2 dă rateuri la monitorizare și cum am rezolvat asta la un proiect cu peste 15k utilizatori activi.
De ce minte PM2?
PM2 monitorizează procesul la nivel de sistem de operare. PID-ul e activ? Da. Consumă memorie? Da. Atunci, pentru PM2, totul e perfect.
Dar Node.js este single-threaded. Dacă ai un loop sincron blocant sau dacă motorul V8 își dă sufletul încercând să facă Garbage Collection pe un heap plin, procesul de sistem rămâne în picioare. PM2 nu are de unde să știe că event loop-ul tău are o latență de 10 secunde și nu mai procesează niciun request HTTP.
Am pățit asta la un API de analytics: un query prost optimizat a încărcat 200MB de JSON în memorie, procesul a intrat în GC-thrashing, CPU-ul a sărit la 100%, iar PM2 zicea relaxat "online". În realitate, toate request-urile primeau timeout.
Soluția: Healthcheck real bazat pe Event Loop Lag
Nu folosi pm2 describe în scripturi de bash rulat în cron-uri. Consumă resurse inutil și tot la nivel de proces raportează. Soluția corectă este să expui un endpoint de /health în aplicație, pe care să îl interoghezi din exterior (sau prin Docker HEALTHCHECK dacă rulezi containerizat).
Iată un exemplu simplu și eficient de endpoint care verifică sănătatea reală a procesului: conexiunea la baza de date și event loop lag-ul. Dacă loop-ul e blocat, codul de mai jos va returna târziu sau deloc, iar monitorizarea va da fail.
Trade-off-uri și ce nu prinde această metodă
Metoda asta e excelentă pentru că măsoară exact ce simte utilizatorul final. Dacă event loop-ul e blocat, healthcheck-ul pică.
Dar are și un minus major de care m-am lovit: cluster mode-ul din PM2. Dacă rulezi PM2 în cluster mode (să zicem cu 4 workeri) și pui un load balancer în față, endpoint-ul /health apelat prin load balancer s-ar putea să lovească workerii sănătoși și să-l rateze pe cel blocat.
Pentru a rezolva asta, ai două variante:
- Configurezi un script local de healthcheck care rulează pe server și interoghează fiecare port intern al workerilor (dacă rulezi instanțe separate pe porturi diferite).
- Folosești un tool de APM (cum e PM2 Plus sau alternative open-source ca Prom-client cu Prometheus) care colectează metrici per worker și trimite alerte când unul dintre ei are lag-ul prea mare.
Voi cum vă asigurați că procesele Node.js chiar răspund la request-uri, dincolo de clasicul status din PM2?