일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- react-router
- JavaScript
- redux-saga
- SW
- redux
- 항해플러스
- 알고리즘
- C++
- 매일메일
- axios
- Get
- redux-toolkit
- 테코테코
- 코딩테스트합격자되기
- 자바
- programmers
- 이코테
- useDispatch
- 항해99
- createSlice
- maeil-mail
- react-redux
- 리액트
- java
- Algorithm
- json-server
- Python
- 프로그래머스
- react
- sw expert academy
- Today
- Total
Binary Journey
[NestJS] NestJS 에서도 GracefulShutdown 구현이 가능할까? 본문
GracefulShutdown 구현이 가능할까?
최근에 스터디에서 Graceful Shutdown 에 대해 언급된 적이 있었는데 NestJS 공부하면서 NestJS에서도 구현이 가능한지 궁금해졌다. (당연히 구현이 가능할 것 같다. 요즘에는 거의 필수로 다들 사용하고 있으니까!)
Graceful Shutdown
Graceful Shutdown은 서버나 애플리케이션이 종료 신호를 받았을 때, 즉시 프로세스를 ‘강제 종료’하지 않고 진행 중인 작업(트랜잭션, 요청 처리, 메시지 소비 등)을 안전하게 마무리한 뒤 종료하도록 설계된 기법이다.
Spring 의 Graceful Shutdown
Spring 환경에서 흔히 볼 수 있는 Graceful Shutdown은 주로 다음과 같은 맥락에서 사용된다
- 마이크로서비스 / 클라우드 환경
Kubernetes나 Docker Swarm 같은 오케스트레이션 환경에서 애플리케이션을 롤링 업데이트하거나 스케일링할 때, 여러 인스턴스를 ‘빠르게 재시작’하거나 ‘종료’하는 상황이 자주 발생한다. 이때 진행 중인 요청이 중단되지 않고 마지막까지 성공적으로 수행될 수 있도록 돕는다. - Zero-downtime 배포
무중단 배포를 위해 새 버전 인스턴스를 기동하고, 구 버전 인스턴스를 종료할 때 연결된 클라이언트나 처리 중인 요청이 완결되길 기다리는 방식이다. - 자원 정리
DB 커넥션 풀, 외부 API 세션, 메시지 큐, 쓰레드 풀 등의 리소스를 안전하게 해제해야 한다.
Spring에서도 Graceful Shutdown을 적용하게 된 이유는 애플리케이션 서버(예: Tomcat, Undertow)가 강제 종료가 아닌 ‘유예 기간(Grace Period)’을 두고, 받던 요청을 천천히 마무리한 뒤 자원을 정리해 종료해주는 구조를 제공하기 때문이다.
Spring에서의 Graceful Shutdown 구현 개념
- 서버 레벨
Spring Boot 2.x부터server.shutdown=graceful
옵션(실험적 기능)으로 설정할 수 있었다. 이 설정을 통해 Spring Boot 내장 서버(Tomcat 등)는 종료 신호(SIGTERM 등)를 받으면 새로운 연결 수용을 중단하고 현재 처리 중인 요청들을 모두 처리 완료할 때까지 대기한다. - 애플리케이션 레벨
코드 차원에서는DisposableBean
인터페이스나@PreDestroy
같은 어노테이션을 활용해 종속 자원을 정리한다.
NestJS에서도 Graceful Shutdown이 가능한지
NestJS 역시 애플리케이션 레벨에서 종료 신호를 감지해 필요한 마무리 작업을 수행할 수 있도록 지원한다.
원리는 Spring과 크게 다르지 않으며, NestJS는 내부적으로 Shutdown Hooks라는 개념을 통해 이를 처리한다.
NestJS 개발 팀이 이 기능을 도입한 이유는 마이크로서비스, 서버리스, 컨테이너 환경에서의 안정적인 배포와 무중단 연동을 위해서다.
실제로 서버가 종료될 때 DB 커넥션, 메시지 큐, 스트리밍 세션 등의 리소스를 안전하게 정리하기 위해 Graceful Shutdown은 필수적인 흐름이 되었다.
NestJS에서 Graceful Shutdown 구현 방법
- 어플리케이션 부트스트랩에서 Shutdown Hooks 활성화
enableShutdownHooks()
를 통해 Node 프로세스가SIGTERM
,SIGINT
같은 신호를 받을 때 NestJS가 내부적으로 모든 종료 관련 인터페이스를 호출하게 된다.
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 종료 신호를 감지하고 NestJS 종료 훅을 트리거
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
- 종료 처리를 위한 라이프사이클 인터페이스 구현
OnApplicationShutdown
BeforeApplicationShutdown
OnModuleDestroy
- 라이프사이클
- 종료 훅이 발생하면, NestJS는 등록된 모든
onApplicationShutdown
메서드를 순차적으로 호출한다. signal
에는 실제로 받은 시그널(SIGTERM
등)이 문자열로 들어오므로, 로깅 등에 활용할 수 있다.BeforeApplicationShutdown
은 실제 애플리케이션이 완전히 종료되기 직전에 호출되며,OnModuleDestroy
는 모듈이 제거될 때 발생한다.
- 종료 훅이 발생하면, NestJS는 등록된 모든
예를 들어, OnApplicationShutdown
인터페이스를 구현하면 다음과 같이 된다.
import { Injectable, OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class SomeService implements OnApplicationShutdown {
onApplicationShutdown(signal?: string) {
// 여기서 DB 연결 종료, 메시지 큐 연결 해제 등의 로직 수행
console.log(`Received shutdown signal: ${signal}`);
}
}
💡 시그널, Signal
시그널(Signal) 은 운영체제(OS)가 프로세스에 보내는 일종의 소식(이벤트)이다.
NestJS에서 Graceful Shutdown을 이야기할 때 자주 등장하는 시그널로는SIGTERM
,SIGKILL
, 그리고SIGINT
등이 있다.
이 시그널들은 서버 프로세스가 종료 또는 중단되어야 할 때 중요한 역할을 한다.SIGTERM
- 운영체제가 프로세스에 “종료하라”라고 요청하는 일반적인 종료 시그널이다.
- 프로세스는 이 시그널을 받았을 때, 종료 절차를 실행하거나 (처리 로직을 등록해 놓았다면) 무시하는 등의 동작을 선택할 수 있다.
-SIGTERM
시그널을 받을 때, NestJS에서는app.enableShutdownHooks()
로 설정된 로직을 실행하고,OnApplicationShutdown
인터페이스를 통해 종료 준비(데이터베이스 연결 해제, 메시지 큐 정리 등)를 수행할 수 있다.
- Graceful Shutdown을 구현하기에 적합한 시그널이다.
- 흔히 컨테이너가 종료되면, 오케스트레이션 툴(Kubernetes 등)에서 SIGTERM을 먼저 보내고, 일정 시간(예: 30초~60초)이 지난 뒤 프로세스가 종료되지 않으면 `SIGKILL`을 보낸다.
- 이를 “Termination Grace Period”라고 부른다. 이 시간 안에 애플리케이션이 SIGTERM을 처리해 종료해야 무중단이 보장된다.SIGKILL
- “즉시 종료”를 의미하는 강제 종료 시그널로, 프로세스가 이 시그널을 받으면 바로 종료된다.
- 프로세스가 이 시그널을 수신하면 어떤 종료 처리 로직도 실행할 수 없다.
- 운영체제에서 가장 강력한 방식으로 프로세스를 죽이는 시그널이다.
- 대개kill -9
명령처럼, 수동으로 강제 종료가 필요할 때 사용된다.SIGINT
- 터미널에서 Ctrl + C를 눌렀을 때 프로세스에 보내지는 시그널이다.
- 일반적으로 “인터럽트(Interrupt) 신호”라고 한다.
- Node.js 애플리케이션에서도 이를 받아서 종료 로직을 수행할 수 있다.
- 종료 프로세스에서 고려해야 할 요소
- DB 트랜잭션
진행 중인 트랜잭션이 중단되지 않도록, 커밋 혹은 롤백을 모두 안전하게 마무리한다. - 메시지 큐(Kafka, RabbitMQ 등)
메시지를 재큐잉하거나, 처리 완료 시점을 보장해야 한다. - 파일 및 메모리 자원
파일 핸들, 캐시 저장소 등도 정리한다. - 헬스 체크
Kubernetes와 같은 환경에서 종료 직전에 readiness probe 상태를 ‘unhealthy’로 바꿔주는 방식도 같이 고려할 수 있다.
- DB 트랜잭션
활용 사례
- 컨테이너 오케스트레이션(Kubernetes, Docker Swarm)
무중단 롤링 업데이트와 스케일링 시, 종료 훅을 통해 안전하게 인스턴스를 내려 효율적 운영이 가능하다. - CI/CD 무중단 배포
API 서버가 요청을 처리 중일 때, 새 버전을 띄우고 구 버전을 종료하는 과정에서 모든 요청이 원활히 마무리된다. - 마이크로서비스
여러 서비스가 상호 연동되어 있을 때, 한 서비스가 종료되더라도 남은 작업을 정상 종료하고 네트워크 연결을 끊을 수 있다.
장단점
장점
- 데이터 일관성 보장
요청이나 트랜잭션이 도중에 끊기지 않아 데이터 무결성을 지킬 수 있다.
DB 연결, 파일 핸들, 스레드 풀 등의 자원 누수를 방지할 수 있다. - 안정적 운영
모니터링, 로깅, 알림 시스템과 결합해 예측 가능한 종료 시나리오를 만들 수 있다.
종료 시점을 제어할 수 있다. - 유연성
NestJS나 Spring 모두 각자 필요한 로직(커넥션 해제, 세션 정리 등)을 커스터마이징해 넣을 수 있다.
단점
- 복잡도 증가
서비스 규모가 커질수록 종료 훅마다 처리해야 할 로직이 늘어나며, 단계별로 꼼꼼히 챙겨야 한다. - 시간 지연
진행 중인 작업을 마무리하기 위해 종료 시간을 늘리는 경우, 과도하게 긴 시간이 소요될 수 있다. - 실수 가능성
모든 자원을 빠짐없이 정리하지 않으면 결국 리소스 누수나 트랜잭션 충돌 같은 문제가 발생할 수 있다.
참고자료
- NestJS 공식문서 - Lifecycle Events
- Spring Boot 문서 - Graceful Shutdown (server.shutdown)
- NestJS Graceful Shutdown
NestJS와 Spring 모두 애플리케이션의 안정성과 무중단 배포를 위해 Graceful Shutdown을 필수 기능으로 지원한다. 올바른 종료 과정을 설계하면, 운영 환경에서 예기치 못한 문제를 줄이고 사용자 경험을 향상시킬 수 있다.