Как понять, что проблема в сервисе связана с Express.js, а не с базой, сетью, инфраструктурой или бизнес-логикой?
Локализация начинается с timing middleware и измерения event loop lag. Синтетический эндпоинт без I/O изолирует Express от внешних зависимостей; дальше — EXPLAIN ANALYZE в БД, CPU profiler и curl с фазовыми таймингами.
Методология локализации проблемы
Диагностика начинается с разделения временных затрат на составляющие. Проблема может находиться в одном из четырёх слоёв: Express/Node.js (CPU, event loop), база данных (медленный запрос, locks), сеть/инфраструктура (latency, DNS, TLS) или бизнес-логика (алгоритм, внешний API).
Шаг 1: Измерить time-to-first-byte на каждом слое
Добавить timing middleware, который записывает длительность каждой фазы:
import { performance } from "perf_hooks";
app.use((req, res, next) => {
const start = performance.now();
res.on("finish", () => {
const duration = performance.now() - start;
// Лог с методом, путём, статусом и временем
console.log(
JSON.stringify({ method: req.method, path: req.path, status: res.statusCode, ms: duration.toFixed(2) })
);
});
next();
});
Если логи показывают, что 95-й перцентиль latency — сотни миллисекунд, а P50 — нормальный, это указывает на периодическую проблему (GC-пауза, lock contention, медленный запрос к внешнему API).
Шаг 2: Проверить event loop lag
Event loop lag >10ms — признак CPU-heavy кода или синхронных операций в обработчике:
import { monitorEventLoopDelay } from "perf_hooks";
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
console.log("EL lag p99 ms:", (h.percentile(99) / 1e6).toFixed(2));
h.reset();
}, 5000);
Если lag стабильно низкий — проблема не в Node.js процессе.
Шаг 3: Изолировать I/O от приложения
Сделать запрос к БД напрямую (psql, Redis CLI, mongosh) и замерить время:
# PostgreSQL: план выполнения с реальными данными
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 42;
# Redis: измерить latency
redis-cli --latency-history -i 1
# curl с таймингами по фазам
curl -w "@curl-format.txt" -o /dev/null -s https://example.com/api/items
curl-format.txt:
time_namelookup: %{time_namelookup}\n
time_connect: %{time_connect}\n
time_appconnect: %{time_appconnect}\n
time_pretransfer: %{time_pretransfer}\n
time_starttransfer: %{time_starttransfer}\n
time_total: %{time_total}\n
Шаг 4: Профилировать Node.js процесс
Если event loop lag высокий — собрать CPU profile:
# Запустить с inspector
node --inspect server.js
# Подключиться в Chrome DevTools -> Performance -> Record
# Или использовать 0x для flame graph
npx 0x -- node server.js
Шаг 5: Проверить инфраструктуру
- DNS-резолюцию внешних зависимостей: медленный DNS приводит к задержкам при первом запросе после TTL.
- Connection pool: исчерпание пула соединений к БД заставляет запросы ждать свободного слота.
- TLS handshake: если upstream HTTPS без keep-alive, каждый запрос платит 100–200ms за handshake.
Как отличить Express от остального
Если добавить эндпоинт без логики и I/O (res.json({ok:true})) и он отвечает быстро — Express и Node.js в порядке, проблема в обработчике. Если и этот эндпоинт медленный — проблема в уровне Node.js/системы (нехватка памяти, swap, перегруженный CPU).
Подводные камни
- Синхронные методы fs (readFileSync, existsSync) в middleware блокируют event loop для всех параллельных запросов.
- Медленный middleware (например, медленная десериализация JWT с RS256 при каждом запросе) сдвигает latency всего сервиса, хотя виновата не бизнес-логика.
- Connection pool PostgreSQL/MySQL по умолчанию мал (pg: 10 соединений) — при высоком параллелизме запросы стоят в очереди внутри приложения, а не на уровне БД.
- DNS-кэш в Node.js отсутствует по умолчанию — каждый запрос к внешнему сервису выполняет резолюцию; dns.lookup с кэшем или использование --dns-result-order=ipv4first решает проблему.
- console.log синхронен в Node.js — при интенсивном логировании каждой строки запроса он может стать узким местом.
- Memory leak в замыканиях middleware (накопление обработчиков событий) вызывает деградацию через GC-паузы, которые выглядят как периодические всплески latency.
- Если сервис работает за nginx, проверить upstream keepalive — без него nginx создаёт новое TCP-соединение на каждый proxied запрос.
What hurts your answer
- Сразу обвинять Express.js, не проверив соседние слои системы
- Чинить симптом без минимального воспроизведения и evidence
- Не учитывать версии, конфигурацию, окружение и recent changes
What they're listening for
- Умеет локализовать проблему вокруг Express.js
- Двигается от симптома к гипотезам и проверкам
- Отличает баг инструмента от ошибки использования или окружения