> Как обнаруживать и реагировать на проблемы с отменой брони (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: sferaplatform.ru

Стек: Go

> Пример ответа

Короткий ответ

Обнаружение проблем с отменой брони строится на трёх уровнях: трассировка отмены через контекст, мониторинг времени выполнения операций и алерт-система на основе метрик. Реагирование - это graceful shutdown, повторные попытки с backoff для идемпотентных операций и компенсационные транзакции. Ключевой момент - не игнорировать отмену, а явно проверять ctx.Err() после каждого потенциально блокирующего вызова и логировать, где именно произошла отмена, с полным стеком операций.

Подробное объяснение

Проблемы с отменой брони в Go обычно проявляются в трёх сценариях:

  1. Потерянная отмена - контекст не пробрасывается через все слои приложения, и операция продолжает выполняться после отмены. Это приводит к лишней нагрузке на БД, внешние сервисы и утечкам горутин.

  2. Зависшая отмена - операция не реагирует на ctx.Done(), потому что использует блокирующие вызовы без учёта контекста (например, time.Sleep, db.Query без контекста, http.Get без контекста).

  3. Частичная отмена - отмена происходит между шагами распределённой операции, и часть шагов уже выполнена, а часть нет. Требуется компенсация.

Обнаружение строится на:

  • Инструментировании: каждый слой приложения логирует ctx.Err() при завершении операции. Если ctx.Err() != nil, это событие для метрик.
  • Метриках: гистограмма времени выполнения операций с тегом canceled=true/false. Резкий рост доли отменённых операций - сигнал к алерту.
  • Трассировке: OpenTelemetry-спаны с атрибутом canceled=true позволяют увидеть, где именно в цепочке вызовов произошла отмена.
  • Структурированном логировании: при отмене логируем operation_id, cancel_reason (deadline exceeded, client closed connection, parent canceled), duration, stack на уровне debug.

Реагирование:

  • Graceful shutdown: при сигнале отмены завершаем текущие операции, но не начинаем новые. Используем errgroup с контекстом для координации.
  • Retry с умным backoff: если операция идемпотентна, повторяем с экспоненциальным backoff, но только если отмена не была вызвана клиентом. Для этого различаем context.Canceled и context.DeadlineExceeded.
  • Компенсация: для распределённых операций храним состояние шагов и при отмене выполняем обратные действия (например, возврат средств, освобождение ресурсов).
  • Circuit breaker: если доля отмен превышает порог, размыкаем цепь и не пускаем новые запросы, чтобы не усугублять нагрузку.

На практике

В реальном сервисе бронирования:

  1. Все репозитории принимают ctx первым аргументом и используют QueryContext, ExecContext, BeginTx с контекстом.
  2. HTTP-хендлеры проверяют r.Context().Err() перед отправкой ответа - если клиент отключился, не пишем в ResponseWriter.
  3. Фоновая обработка (например, подтверждение брони через email) использует errgroup.WithContext и при отмене отменяет все дочерние задачи.
  4. Метрики: canceled_operations_total{operation, reason} и operation_duration_seconds{operation, canceled}.
  5. Алерт: если rate(canceled_operations_total[5m]) > 0.1 * rate(operations_total[5m]) - паника в сервисе или проблема с upstream.

Пример реакции на отмену в middleware:

GO
func CancelMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
// клиент отключился - логируем и прекращаем работу
log.WithContext(ctx).Warn("client disconnected",
"path", r.URL.Path, "err", ctx.Err())
// не пишем в w - это вызовет panic, если уже писали
case <-done:
}
}()
next.ServeHTTP(w, r.WithContext(ctx))
close(done)
})
}

Как отвечать на собеседовании

Начните с того, что отмена - это не ошибка, а нормальное состояние системы. Покажите, что различаете context.Canceled и context.DeadlineExceeded - это разные сценарии с разной реакцией. Обязательно упомяните, что проверка ctx.Err() должна быть после каждого блокирующего вызова, а не только в начале функции. Расскажите про метрики и алерты - интервьюер хочет услышать, что вы думаете о наблюдаемости, а не только о коде. Хорошо добавить пример из практики: как вы находили утечку горутин из-за потерянного контекста через pprof и трассировку.

Что проверяет интервьюер

  • Понимание жизненного цикла контекста и его проброса через все слои.
  • Умение различать типы отмен и выбирать стратегию реакции.
  • Навыки наблюдаемости: метрики, логи, трассировка.
  • Понимание компенсационных транзакций в распределённых системах.
  • Практический опыт с errgroup, context.WithTimeout, context.WithCancel.

Типичные ошибки

  • Проверка ctx.Err() только в начале функции - отмена может произойти в середине.
  • Использование time.Sleep вместо select с ctx.Done().
  • Игнорирование отмены в фоновых горутинах - они продолжают работать после завершения запроса.
  • Retry без проверки причины отмены - повторяем даже когда клиент ушёл.
  • Логирование отмены как ошибки - это засоряет алерты и скрывает реальные проблемы.
  • Не различать context.Canceled и context.DeadlineExceeded - это разные сценарии: клиент отключился vs таймаут.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью