Как работает graceful shutdown в backend-приложениях

8 минут чтения
Средний рейтинг статьи — 4.9

В продакшене backend-приложения регулярно перезапускаются: деплой новой версии, автоскейлинг, обновления системы или аварийные рестарты. Если в этот момент приложение просто завершается, оно может оборвать активные запросы, потерять данные или оставить систему в неконсистентном состоянии.

Для решения этой проблемы используется graceful shutdown — корректное завершение работы сервиса.

Что такое graceful shutdown

Graceful shutdown — это процесс управляемого завершения приложения, при котором оно:

  • перестаёт принимать новые запросы;
  • корректно обрабатывает текущие;
  • освобождает ресурсы;
  • завершает работу без потери данных.

В отличие от «жёсткого» завершения (kill -9), graceful shutdown даёт приложению время привести себя в порядок.

Как ОС сообщает приложению о завершении

В Linux завершение сервиса обычно начинается с отправки сигналов:

  • SIGTERM — стандартный сигнал на корректное завершение;
  • SIGINT — прерывание (например, Ctrl+C);
  • SIGKILL — немедленное завершение (не перехватывается).

Системы оркестрации (systemd, Docker, Kubernetes) сначала отправляют SIGTERM и ждут заданное время. Если приложение не завершилось — используется SIGKILL.

Graceful shutdown строится вокруг обработки SIGTERM/SIGINT.

Общий алгоритм graceful shutdown

Независимо от языка, логика примерно одинаковая:

  1. Получить сигнал завершения.
  2. Перестать принимать новые соединения.
  3. Дать текущим запросам завершиться.
  4. Закрыть соединения с БД, очередями, внешними сервисами.
  5. Завершить процесс.

Важно, чтобы этот процесс был ограничен по времени, иначе сервис может «зависнуть» при выключении.

Graceful shutdown в Node.js

В Node.js сигналы можно перехватывать через process.on:

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

Типичный пример с HTTP-сервером:

const http = require('http');
 
const server = http.createServer((req, res) => {
  setTimeout(() => {
    res.end('ok');
  }, 1000);
});
 
server.listen(3000);
 
function shutdown() {
  console.log('Shutting down...');
  
  server.close(() => {
    console.log('HTTP server closed');
    process.exit(0);
  });
 
  setTimeout(() => {
    console.error('Force shutdown');
    process.exit(1);
  }, 10000);
}

Что происходит:

  • server.close() перестаёт принимать новые соединения;
  • активные запросы завершаются;
  • по таймауту происходит принудительное завершение.

Важно помнить, что Node.js — однопоточный, и блокирующие операции могут сорвать graceful shutdown.

Работа с базами данных и очередями в Node.js

При завершении приложения нужно явно закрывать ресурсы:

  • пул соединений с БД;
  • подключения к Redis;
  • consumer’ы очередей (Kafka, RabbitMQ).

Пример:

await db.close();
await redis.quit();

Если этого не сделать, процесс может не завершиться или потерять данные.

Graceful shutdown в Go

В Go стандартная библиотека предоставляет удобные инструменты для этого.

Перехват сигналов:

ctx, stop := signal.NotifyContext(
    context.Background(),
    os.Interrupt,
    syscall.SIGTERM,
)
defer stop()

Пример с HTTP-сервером:

server := &http.Server{
    Addr: ":8080",
    Handler: handler,
}
 
go server.ListenAndServe()
 
<-ctx.Done()
 
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
 
server.Shutdown(shutdownCtx)

Метод Shutdown:

  • прекращает приём новых соединений;
  • ждёт завершения активных запросов;
  • корректно закрывает сервер.

Это один из примеров, где Go даёт более безопасный и выразительный API «из коробки».

Контексты и отмена операций в Go

Ключевая идея graceful shutdown в Go — контексты.

Любая долгоживущая операция должна принимать context.Context и корректно реагировать на его отмену:

select {
case <-ctx.Done():
    return ctx.Err()
default:
    // работа
}

Без этого сервер может зависнуть при завершении.

Таймауты — обязательная часть

Graceful shutdown не должен длиться бесконечно.

Хорошая практика:

  • 5–10 секунд для HTTP-сервисов;
  • меньше для фоновых воркеров;
  • больше — только для критичных batch-задач.

Таймаут защищает систему от «залипших» соединений и некорректного кода.

Частые ошибки

На практике часто встречаются такие проблемы:

  • SIGTERM не обрабатывается вообще;
  • сервер продолжает принимать новые запросы;
  • нет таймаута завершения;
  • не закрываются соединения с БД;
  • фоновые горутины или таймеры продолжают работать.

Все эти ошибки проявляются только в продакшене — при деплоях и масштабировании.

Итоги

Graceful shutdown — обязательный элемент надёжного backend-приложения.

Ключевые моменты:

  • всегда обрабатывайте SIGTERM и SIGINT;
  • прекращайте приём новых запросов;
  • завершайте активные операции корректно;
  • используйте таймауты;
  • тестируйте shutdown так же, как и обычные сценарии.

Корректный graceful shutdown снижает количество ошибок при деплоях, улучшает стабильность системы и делает сервисы готовыми к реальной эксплуатации.

8 минут чтения
Средний рейтинг статьи — 4.9

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний