const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
let lastDbCheck = 0;
let isDbHealthy = true;
router.get('/healthz', async (req, res) => {
const now = Date.now();
// Verificăm DB doar o dată la 30 de secunde ca să nu facem spam
if (now - lastDbCheck > 30000) {
try {
// 1 înseamnă conectat în driverul de Mongoose
isDbHealthy = mongoose.connection.readyState === 1;
lastDbCheck = now;
} catch (err) {
isDbHealthy = false;
}
}
// Verificăm dacă Event Loop-ul este extrem de încetinit
const start = Date.now();
setTimeout(() => {
const lag = Date.now() - start;
if (lag > 200) {
return res.status(503).json({ status: 'unhealthy', reason: 'event_loop_lagging', lag });
}
if (!isDbHealthy) {
return res.status(503).json({ status: 'unhealthy', reason: 'database_disconnected' });
}
return res.status(200).json({ status: 'healthy', uptime: process.uptime() });
}, 0);
});
module.exports = router;Am pățit-o acum vreo doi ani pe un server VPS unde rulam un API destul de solicitat, cam 12k utilizatori activi zilnic. PM2 arăta frumos, verde, status "online" de 14 zile. În realitate? Clienții primeau timeout-uri pe bandă rulantă. Baza de date își atinsese limita de conexiuni, iar Event Loop-ul era complet blocat de o funcție sincronă scrisă prost. PM2 n-avea nicio treabă, el vedea doar că procesul OS există și are un PID valid.
Dacă te bazezi doar pe clasicul pm2 status, te minți singur. Hai să vedem ce ratează monitorizarea de bază și cum pui la punct un sistem de siguranță care chiar funcționează în producție.
Ce ratează PM2 când îți zice că e "online"
PM2 monitorizează procesul din exterior, la nivel de sistem de operare. Pentru el, atâta timp cât procesul Node.js nu a dat crash cu un uncaughtException sau un semnal de kill (cum e Out of Memory), totul e perfect.
Dar aplicația ta poate fi o legumă în stare vegetativă din mai multe motive:
- Event Loop blocat: O operațiune CPU-intensive sau un regex prost scris blochează thread-ul unic. Procesul e viu, dar nu mai răspunde la niciun request HTTP.
- Conexiuni moarte la DB: Pool-ul de conexiuni s-a blocat sau baza de date a picat temporar, dar driverul de Node nu a făcut crash la proces, ci doar aruncă erori la fiecare query.
- Memory Leaks asimptomatice: Consumul de RAM crește încet, răspunsurile devin din ce în ce mai leneșe, dar nu s-a atins încă limita de heap pentru crash.
Cum construiești un endpoint de healthcheck util
Soluția nu e să complici infrastructura, ci să expui un endpoint dedicat (de exemplu /healthz) care să fie interogat de un serviciu extern de monitorizare (Uptime Kuma, AWS Route53 sau chiar un script local).
Dar atenție la un trade-off major. Dacă pui healthcheck-ul să facă SELECT 1 în baza de date la fiecare 5 secunde, s-ar putea să încarci inutil DB-ul la scale mare. Recomandarea mea e să verifici conexiunea la DB o dată la 30 de secunde (folosind un cache de stare) sau să te bazezi pe starea internă a driverului (de exemplu, starea pool-ului din Mongoose sau Knex).
În codul de mai jos am pus un exemplu simplu de Express care prinde atât blocajele de Event Loop, cât și problemele de bază de date, fără să pună serverul în cap.
Cum legi asta de PM2 sau de monitorizarea externă
Dacă rulezi PM2 pe un server simplu (fără Docker sau Kubernetes), ai două variante mari pentru a acționa când healthcheck-ul pică.
Cea mai simplă variantă este să folosești un script cron local care rulează la fiecare minut. Scriptul dă un curl rapid la portul local. Dacă primește altceva decât HTTP 200, rulează un pm2 reload <app_name>.
O altă variantă, mai elegantă dacă folosești un load balancer în fața PM2 (cum e Nginx), este să configurezi Nginx să scoată instanța din pool dacă /healthz nu mai răspunde. Astfel, userii nu vor simți deloc downtime-ul în timp ce PM2 (sau tu, manual) repornește procesul blocat.
La final, regula de aur e simplă: dacă monitorizarea ta nu testează fluxul de date esențial, atunci nu monitorizezi aplicația, ci doar un proces care consumă curent degeaba.
Voi cum gestionați treaba asta în producție pe PM2? Vă bazați doar pe restart-ul automat la crash sau aveți ping-uri HTTP externe puse la punct?