배포할 때마다 처리 중이던 요청이 끊기고 OnModuleDestroy가 안 불린 이유
문제 발생
배포 때마다 진행 중이던 요청이 502로 끊기고, 큐 컨슈머는 처리 중이던 메시지를 잃었습니다. 정리 코드를 분명히 넣어뒀는데 로그가 한 줄도 남지 않았습니다.
@Injectable()
export class QueueConsumer implements OnModuleDestroy {
async onModuleDestroy() {
await this.channel.close(); // 실행되지 않음
}
}원인 분석
종료 훅이 켜져 있지 않았습니다. NestJS 문서가 짚는 그대로 — 종료 훅은 먼저 활성화해야 하며, 부트스트랩에서 app.enableShutdownHooks()를 명시적으로 호출해야 합니다. 기본으로 동작하지 않습니다.
기본값이 꺼짐인 이유도 문서에 있습니다 — enableShutdownHooks는 리스너를 등록하므로 메모리를 소비하며, 한 프로세스에서 여러 Nest 인스턴스를 띄우는 병렬 테스트에서는 Node 경고가 날 수 있습니다.
켜고 나면 종료 순서도 정해져 있습니다. 애플리케이션이 종료 시그널을 받으면 등록된 종료 메서드들이 순차적으로 호출되고, 어떤 핸들러가 비동기 작업을 반환하면 그 프로미스가 완료될 때까지 다음 순서로 넘어가지 않습니다.
해결 방안
- 부트스트랩에서 켭니다.
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);-
정리 작업을 올바른 훅에 나눠 담습니다. 문서가 제공하는 인터페이스는
OnModuleInit,OnApplicationBootstrap,OnModuleDestroy,beforeApplicationShutdown,OnApplicationShutdown입니다. 새 작업 수신을 멈추는 일과 커넥션을 닫는 일은 단계가 다릅니다. -
컨테이너 환경의 시그널을 확인합니다. Docker/Kubernetes는
SIGTERM을 보냅니다. 문서는 Windows에서SIGINT,SIGBREAK, 부분적으로SIGHUP만 지원되고SIGTERM은 OS 제약으로 특별 처리되지 않는다고 적습니다 — 로컬이 Windows라면 이 차이 때문에 재현되지 않습니다. -
훅에 무한정 기다리는 코드를 두지 않습니다. 오케스트레이터의 유예 시간(
terminationGracePeriodSeconds등)을 넘기면 프로세스는 어차피 강제 종료됩니다. 정리에도 타임아웃을 겁니다. -
테스트에서는 켜지 않는 편이 낫습니다. 문서가 경고하는 리스너 누적이 그대로 나타납니다. 애플리케이션 부트스트랩 경로에서만 호출합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.