awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

Async vs blocking: чтение кода и трейсов

Суть Читай реальные Node-обработчики и сигнал perf, предсказывай, как каждый взаимодействует с event loop, и выбирай фикс, к которому первым тянется сеньор.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Блокировку диагностируют в обработчиках и в гистограмме lag, а не абстрактно. Читай каждый сниппет, предсказывай, что он делает с единственным потоком loop, и выбирай изменение, которое сеньор сделал бы прежде, чем тянуться к любому knob.

Цель

Отработай цикл, который ты запускаешь в каждом инциденте заморозки: замечай синхронный участок или неограниченный fan-out на горячем пути, называй, почему он стопорит loop, и тянись к фиксу с наибольшим рычагом — async API, worker thread или лимит конкурентности.

Сниппет 1 — синхронный логин

import bcrypt from "bcrypt";
import fs from "fs";

app.post("/login", (req, res) => {
  const policy = fs.readFileSync("./password-policy.json", "utf8"); // sync read
  const hash = bcrypt.hashSync(req.body.password, 12);              // ~250 ms CPU
  // ...verify and respond...
});
Викторина

Под нагрузкой логинов этот обработчик роняет пропускную способность каждого route, не только /login. Что происходит и фикс с наибольшим рычагом?

Сниппет 2 — ресайз на пуле libuv

// "Фикс" команды после профилирования медленного эндпоинта картинок:
process.env.UV_THREADPOOL_SIZE = "32";

app.post("/resize", (req, res) => {
  const out = resizeImageSync(req.body.buffer, 1024, 768); // pure-JS pixel loop
  res.send(out);
});
Викторина

Команда подняла UV_THREADPOOL_SIZE до 32, ожидая распараллелить ресайз. Ничего не изменилось. Почему и какой фикс верный?

Сниппет 3 — голодание worker-пула

// Worker-пул под 4 ядра машины:
const pool = new WorkerPool({ size: 4 });

app.get("/report/:id", async (req, res) => {
  // Каждый отчёт = одна CPU-тяжёлая задача агрегации на пуле.
  const result = await pool.run("aggregate", req.params.id); // may take ~2 s
  res.json(result);
});
Викторина

Под всплеском запросов отчётов каждый эндпоинт — включая дешёвые, тоже использующие пул — видит рост задержки до секунд, затем таймауты. Какой это режим отказа?

Сниппет 4 — измеряем заморозку

import { monitorEventLoopDelay } from "node:perf_hooks";

const h = monitorEventLoopDelay();
h.enable();
setInterval(() => {
  console.log("loop delay p99 (ms):", h.percentile(99) / 1e6);
}, 1000);
// Пример вывода во время плохого запроса отчёта:
// loop delay p99 (ms): 812
Викторина

CPU спокойно сидит на ~55%, пока это печатает p99 loop delay 812 мс. Какое прочтение верно?

Итог

Любую заморозку читают в обработчиках и в гистограмме lag: синхронный I/O и sync crypto на пути запроса стопорят loop и должны переехать на async API (которые используют пул libuv); CPU-bound JS не лечится большим пулом libuv и живёт в worker thread; фиксированный worker-пул голодает под всплеском тяжёлых задач, поэтому ограничивай очередь таймаутами и держи быстрые задачи вне него; а event-loop delay — а не CPU — это метрика, совпадающая с таймаутами, которые чувствуют пользователи. Диагностируй по сигналу, чини блокирующий участок, затем перемерь.

Продолжить восхождение ↑Async vs blocking: разморозь loop
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.