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

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

Собираем воедино: чтение кода и инцидентов

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

Составные сбои диагностируют в коде и логах, а не в абстракции. Прочитай каждый сниппет, предскажи его поведение под нагрузкой и выбери фикс с наибольшим рычагом — тот, что закрывает взаимодействие, а не только локальный симптом.

Цель

Отработай цикл, который ты крутишь в каждом инциденте: прочитать горячий путь, заметить, чьё плохое поведение питает следующий механизм, и потянуться к фиксу, останавливающему каскад у истока.

Сниппет 1 — обработчик, блокирующий loop

// Node.js, один event loop, тысячи конкурентных запросов
app.post("/report", (req, res) => {
  const body = req.body;                       // уже распарсено
  const hash = crypto.pbkdf2Sync(              // СИНХРОННО, ~80мс
    body.token, body.salt, 200000, 64, "sha512"
  );
  const pdf = renderPdfSync(body.rows);        // СИНХРОННО, ~120мс CPU
  res.send({ ok: true, digest: hash.toString("hex"), pdf });
});
Викторина

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

Сниппет 2 — acquire из pool без границы

// Go: фиксированный pool из 50 соединений, без acquisition timeout
func (h *Handler) Charge(ctx context.Context, req ChargeReq) error {
    conn := h.pool.Acquire()          // блокируется бесконечно, если pool пуст
    defer h.pool.Release(conn)
    // у вызова провайдера тоже нет своего таймаута
    resp, err := h.provider.Call(conn, req)
    if err != nil {
        return err
    }
    return h.repo.Save(conn, resp)
}
Викторина

Провайдер замедлился до 4 с на вызов. Что этот код делает со всем сервисом и каков первый фикс?

Сниппет 3 — неатомарная проверка идемпотентности

-- Приложение делает: check-then-insert, два отдельных оператора
SELECT result FROM charges WHERE idempotency_key = $1;     -- шаг A: не найдено
-- ... обработчик идёт списывать с карты ...
INSERT INTO charges (idempotency_key, result) VALUES ($1, $2);  -- шаг B
Викторина

Клиент повторяет тот же POST, и два запроса идут конкурентно. С этой check-then-insert идемпотентностью что произойдёт и как это починить?

Сниппет 4 — breaker, который не срабатывает

const breaker = new CircuitBreaker(callProvider, {
  errorThresholdPercentage: 50,   // открыть при 50% ошибок
  resetTimeout: 30000,            // half-open через 30с
  // опция `timeout` не задана — вызовы могут висеть бесконечно
});

async function charge(req) {
  return breaker.fire(req);       // await callProvider без дедлайна
}
Викторина

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

Итог

Каждый сниппет — это один механизм, чей изъян становится катастрофой следующего: синхронный CPU на event loop стопорит всю конкурентность; безграничный acquire из pool превращает медленный downstream в полный стопор; неатомарная проверка идемпотентности списывает дважды под гонкой retry; и breaker без таймаута вызова слеп к зависшим вызовам. Фикс всегда тот, что останавливает взаимодействие у истока — унести работу с loop, fail-fast на acquire, атомарный dedup и таймаут для breaker — а не ручка, откладывающая или усиливающая проблему.

Продолжить восхождение ↑Собираем воедино: собери устойчивый backend-сервис
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.