const express = require('express');
const router = express.Router();
const db = require('./db');
let isTooBusy = false;
let lastCheck = Date.now();
// Monitorizăm event loop lag brut, fără librării externe
setInterval(() => {
const now = Date.now();
const lag = now - lastCheck - 1000;
isTooBusy = lag > 300; // lag mai mare de 300ms înseamnă blocaj
lastCheck = now;
}, 1000).unref();
router.get('/health', async (req, res) => {
if (isTooBusy) {
return res.status(503).send('Event loop blocked');
}
try {
// Verificăm rapid baza de date cu un timeout de 2 secunde
await Promise.race([
db.query('SELECT 1'),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout DB')), 2000))
]);
res.status(200).send('OK');
} catch (err) {
console.error('Healthcheck failed:', err.message);
res.status(500).send('Database connection error');
}
});PM2 e genial până când nu mai e. Toți am trecut prin asta: PM2 zice verde ("online"), dar clienții urlă pe Slack că site-ul e jos și dă 504 Gateway Timeout.
De ce ne minte PM2?
Am pățit-o acum vreo doi ani pe un proiect cu 14.000 de utilizatori activi în timp real. Serverul Node.js intrase într-un loop infinit din cauza unui regex scris prost pe un endpoint de căutare. CPU-ul era la 100%, event loop-ul era complet blocat, dar în consolă pm2 status raporta mândru statusul "online".
De ce? Pentru că PM2 verifică doar dacă procesul în sine există în tabela sistemului de operare. Dacă procesul e viu (nu a dat crash cu exit code mare), pentru PM2 totul e brici. Nu-i pasă că aplicația ta nu mai răspunde la niciun request HTTP sau că pool-ul de conexiuni la baza de date a crăpat demult.
Ce trebuie să verifice un healthcheck real
Ca să nu mai fii sunat la 3 dimineața, ai nevoie de un endpoint dedicat de /health pe care să-l interoghezi din exterior (din load balancer sau dintr-un script local). Dar atenție: un simplu res.send('OK') nu e destul, pentru că ăla doar îți zice că Express-ul încă poate ruta un request simplu.
Un healthcheck serios trebuie să valideze trei lucruri mari:
- Conexiunea la baza de date (un simplu
SELECT 1e suficient). - Event loop lag-ul. Dacă lag-ul e prea mare, aplicația e semi-moartă și nu mai poate procesa request-uri noi.
- Starea conexiunilor externe critice (Redis, API-uri de plată etc.).
Trade-off-ul de care te lovești rapid
Dacă pui load balancer-ul (AWS ALB, Cloudflare, Nginx) să apeleze /health la fiecare 5 secunde și tu faci interogări în baza de date la fiecare apel, o să-ți încarci DB-ul aiurea. La 10 instanțe de Node, înseamnă zeci de interogări inutile pe secundă.
Soluția pe care am aplicat-o a fost să facem cache la rezultatul healthcheck-ului pentru 3 secunde. Astfel, primul request face interogarea reală, iar următoarele din acel interval primesc direct rezultatul din memorie.
Dacă lag-ul e prea mare sau baza de date dă eroare, returnăm un status non-200. Atunci load balancer-ul scoate instanța din uz, iar noi putem rula un pm2 reload controlat.
Voi cum gestionați asta în producție? Vă bazați doar pe PM2 sau aveți un mecanism extern care verifică starea reală a procesului?