const express = require('express');
const router = express.Router();
const db = require('../db');
// Liveness: verifică dacă Event Loop-ul procesează request-uri
router.get('/health/liveness', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: Date.now() });
});
// Readiness: verifică dependențele critice înainte de a trimite trafic
router.get('/health/readiness', async (req, res) => {
try {
// Timeout scurt pentru a nu bloca apelul
await db.query('SELECT 1', [], { timeout: 2000 });
res.status(200).json({
status: 'ready',
db: 'connected',
uptime: process.uptime()
});
} catch (error) {
res.status(503).json({
status: 'degraded',
error: error.message
});
}
});
module.exports = router;Am văzut prea multe servicii în producție marcate cu "online" în PM2, dar care dădeau erori 500 pe bandă rulantă. PM2 verifică strict dacă procesul de Node.js are un PID activ în sistemul de operare, nu și dacă aplicația e capabilă să proceseze request-uri. În articolul ăsta discutăm despre cum construim un healthcheck real, ce verificări merită făcute și ce capcane trebuie să eviți.
Marea minciună din PM2 status online
Acum ceva timp, la o aplicație SaaS cu vreo 14.000 de utilizatori zilnici, am avut un incident urât. Pool-ul de conexiuni la PostgreSQL s-a blocat din cauza unui leak de conexiuni neînchise dintr-un worker. În terminal, pm2 status afișa un verde frumos, starea proceselor era online, iar CPU-ul era la 0%.
În realitate, fiecare request către API agăța timp de 30 de secunde și se termina cu Nginx 504 Gateway Timeout. PM2 nu repornea procesul pentru că, din punctul de vedere al kernel-ului de Linux, procesul Node.js era în viață. Nu făcea crash, nu dădea uncaughtException, pur și simplu stătea și aștepta resurse care nu mai veneau.
Monitorizarea nativă din PM2 fără abonamentele lor de enterprise este oarbă la trei lucruri majore:
- Blocarea Event Loop-ului (task-uri lungi pe CPU sau bucle infinite).
- Pierderea conexiunilor la servicii terțe (baza de date, Redis, API-uri externe).
- Memory leaks care încă nu au atins limita de
max_memory_restart.
Liveness vs. Readiness: Ce trebuie să testezi
Soluția este să separi testarea stării în două concepte pe care le cunoaștem din Kubernetes, dar pe care le putem aplica ușor și într-o stivă clasică cu PM2:
- Liveness (
/health/liveness): Răspunde instant cu 200 OK dacă Event Loop-ul nu e complet blocat. Nu interoghează baza de date, nu face nimic greu. Dacă ăsta dă timeout sau 500, procesul e mort clinic și trebuie restartat. - Readiness (
/health/readiness): Verifică dacă aplicația poate deservi trafic real. Face un query rapid pe baza de date și un ping pe Redis.
Aici apare însă un trade-off important. Dacă faci verificările de readiness prea des (de exemplu, un ping de DB la fiecare 2 secunde pe 8 clustere PM2), introduci un overhead inutil. Pe o infrastructură mai veche, am văzut cazuri unde 20% din totalul query-urilor trimise către Postgres erau generate exclusiv de scriptul de healthcheck.
Cum legi healthcheck-ul de restartul automat
Din păcate, PM2 Community Edition nu are un modul nativ grozav care să apeleze HTTP endpoints și să dea restart automat când o rută returnează 500. Soluția curată pe care o folosesc de 4-5 ani încoace este un script simplu de bash (sau un cron job separat) legat la pm2 reload.
Scriptul apelează endpoint-ul de readiness o dată la 15-30 de secunde. Dacă primește orice altceva decât HTTP 200 de două ori la rând, declanșează pm2 reload app-name --graceful-reload. Așa ai zero downtime pentru clienți și rezolvi problema proceselor care stau blocate.
Partea bună e că, folosind un graceful reload, PM2 va porni o instanță nouă, va aștepta să devină pregătită și abia apoi o va opri pe cea veche care avea conexiunile blocate.
Voi cum gestionați cazurile în care aplicațiile Node stau blocate în online? Folosiți PM2 Plus, Docker healthchecks sau aveți propriile scripturi interne?