본문 바로가기
개발/서버

[ 프차연구소 ] graceful shutdown 설정하기

by agong이 2026. 1. 25.

 

 

[ 프차연구소 ] blue-green 무중단 배포 적용

프랜차이즈연구소데이터로 분석하는 똑똑한 프랜차이즈 창업, 프랜차이즈연구소fchalab.com 서비스 초기에는 잦은 배포가 진행됩니다. 이때 서버가 종료되고 새롭게 실행되는 과정에서 일시적으

agongstory.tistory.com

이전 글에서 blue-green 배포를 하며 docker 컨테이너를 바로 종료하는 것이 아닌 30초의 대기시간을 두고 종료하였다.

이번에는 스프링에서도 동일하게 graceful shutdown을 설정하고자 한다. 

먼저 현재 배경은 다음과 같다.

    - 서비스 상태: Blue-Green 무중단 배포가 적용된 Spring Boot 애플리케이션.
    - 인프라 환경: 단일 EC2 인스턴스에서 Docker 컨테이너로 운영 중. Nginx 리버스 프록시 사용.
    - 배포 방식: GitHub Actions를 통한 CI/CD. 구 컨테이너 종료 시 `docker stop --time 50` 사용.
    - 현재 문제: Spring Boot에 Graceful Shutdown이 설정되어 있지 않아, 배포 시 진행 중인 요청이 강제로 끊길 수 있음.

 

참고로 SpringBoot 3.4 버전 이상부터는 default로 graceful이 설정되었다고 한다. 하지만 시간을 커스텀하기 위해 이번 글에서 설정하고자한다.

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s # default:30s
    
server:
  shutdown: graceful # Springboot 3.4 이상부터는 default

설정 자체는 매우 간단하다. 이렇게 설정하면 기존 요청을 처리하고 안정적으로 종료된다. 만약 요청 처리가 너무 길어지게 되면 계속 기다릴 수 없기 때문에 종료 대기 timeout 시간(timeout-puer-shutdown-phase)을 지정할 수도 있다.

 

이렇게 설정하면 우리 서비스의 종료 흐름은 다음과 같다.

 

1. docker stop '오래된 버전'

docker stop $OLD_NAME --time 50

워크플로우에서 해당 명령어가 실행되면 docker는 SIGTERM을 SpringBoot에게 보냄. 이때 50초까지 프로세스가 종료되기를 기다려 준다.

 

docker container stop

 

docs.docker.com

 

 

2. Spring 서버 graceful shutdown

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s # default:30s

스프링은 기존 요청을 마무리할 시간 최대 30초를 기다려줌. 초과 시에는 더 이상 무기한 대기하지 않고 종료 절차를 계속 진행한다.

 

만약 docker에서 설정하는 timeout 시간이(현재 50초) Spring 에서 설정한 timeout 시간(30s)보다 짧다면 스프링 서버가 안정적으로 종료되기도 전에 도커에서 프로세스를 강제종료(SIGKILL)할 수 도 있다. 그렇기에 Spring에서 설정한 timeout 시간보다 더 길어야한다.

 

그렇다면 어떻게 모든 요청이 처리되었다는것을 확인할까? 이를 확인하는것은 Tomcat(WAS)이 수행한다. Tomcat은 평소에도 커넥션을 관리하며 현재 처리 중인 요청의 개수를 확인하고 있다. Tomcat(WAS)의 graceful은 SmartLifecycle stop 단계에서 수행되며, 그 내부에서 Tomcat이 active request가 0이 될 때까지(또는 timeout까지) 대기한다.

 

[SpringBoot] 멀티 스레드 기반으로 다중 요청을 처리하는 톰캣(Tomcat)의 구조와 동작 방식

1. 멀티 스레드 기반으로 다중 요청을 처리하는 톰캣(Tomcat)의 구조와 동작 방식[ 웹 애플리케이션 서버(WAS, Web Application Server)과 톰캣 ]스프링 MVC 프레임워크는 자바 엔터프라이즈 개발을 편리하

mangkyu.tistory.com

 

그렇다면 비동기 로직이나 스케줄러를 통해 실행되는 로직은 어떨까? 비동기 스레드는 별도의 설정이 없다면 여전히 즉시 강제종료 하게 된다. 우리 서비스에는 AI 채팅, 데이터 동기화시에 비동기로 동작하는 로직이 존재하였기에 문제가 발생할 수 있었다.

이를 위해 추가로 설정을 일부 진행하였다.

 

1. ThreadPoolTaskExecutorBuilder를 활용하여 비동기 스레드 풀을 생성

 

ThreadPoolTaskExecutorBuilder (Spring Boot 4.0.2 API)

acceptTasksAfterContextClose

docs.spring.io

 

기존에는 new ThreadPoolTaskExecutor()를 활용하여 비동기 스레드 풀을 생성하였다. 하지만 ThreadPoolTaskExecutorBuilder를 사용하여 생성한다면 일반적인 ThreadPoolTaskExecutor 설정을 application.yml에 설정하고 이를 지정할 수 있다.

 

2. application.yml 설정

spring:
  # 비동기 작업 Graceful Shutdown 설정
  task:
    execution:
      shutdown:
        await-termination: true        # 종료 시 실행 중인 작업 완료 대기
        await-termination-period: 30s

 

이렇게 설정하면 비동기 작업 또한 Graceful Shutdown 설정이 됩니다. 설정된 시간까지는 실행중인 비동기 작업이 종료되기를 기다린다. 

 

설정 자체는 마찬가지로 간단하지만 깊게 파면 팔수록 다양한 개념에 대해 학습이 필요하다. 관련된 깊은 내용은 참고 링크들을 들어가보면 더 자세하게 알수있다.

 

Spring boot 종료 전체 흐름은 다음과 같다.

 이러한 흐름이기 때문에 phase1에서 최대 30s가 진행하고 phase 2가 시작된다. 각 Phase는 순차적으로 실행되고, 각각 독립적인 타이머가 돌아간다. 그렇기에 최악의 경우 30s + 30s 최대 1분 이상이 걸리는거다.

 

 

이제 실테 테스트 해볼 차례이다.

실제 테스트 시 단순히 10초를 sleep하는 비동기 메서드를 만들고 테스트 시 로그는 다음과 같다.

먼저 더이상 처리할 요청이 없기 때문에 tomcat이 gracefulShutdown이 완료되었다는 로그 후에 비동기 작업이 모두 완료되고 graceful shutdown이 완료되었다는것을 알수있다.

 

2026-01-25 01:41:30.024 +0900  INFO 244564 --- [franchiseLabServer] [nio-8080-exec-2] c.z.f.test.ShutdownTestController        : [테스트] 비동기 작업 트리거 요청 수신
2026-01-25 01:41:30.025 +0900  INFO 244564 --- [franchiseLabServer] [anchise-async-1] c.z.f.test.ShutdownTestService           : [테스트] 비동기 작업 시작 (10초 소요 예정)
2026-01-25 01:41:33.957 +0900  INFO 244564 --- [franchiseLabServer] [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2026-01-25 01:41:34.444 +0900  INFO 244564 --- [franchiseLabServer] [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
2026-01-25 01:41:40.036 +0900  INFO 244564 --- [franchiseLabServer] [anchise-async-1] c.z.f.test.ShutdownTestService           : [테스트] 비동기 작업 정상 완료!
2026-01-25 01:41:40.038 +0900  INFO 244564 --- [franchiseLabServer] [ionShutdownHook] c.z.f.chat.service.ChatStreamService     : Heartbeat 스케줄러 종료

 

참고로 비동기 작업이 완료되고나서야 Heartbeat 스케줄러도 정리되는 로그를 확인 할 수있다. 이는 Phase 2에서 Spring이 ChatStreamService 빈을 정리할 때, 직접 짠 정리 코드가 실행되도록 설정해놨기 때문에 다음과 같은 로그가 출력되는 것이다. 이때 Heartbeat는 이 서비스에서만 쓰이고, 다른 곳에서 재사용할 일이 없기 때문에 Bean으로 만들지 않았음. spring bean으로 관리되지 않기 때문에 별도의 graceful 한 종료처리를 해놨다. 앞선 비동기 작업에 대한 Graceful Shutdown 설정은 Spring Bean에만 적용된다.

public class ChatStreamService {

    /**
     * Heartbeat 주기 (초): Nginx 유휴 타임아웃(60초) 방지용
     */
    private static final long HEARTBEAT_INTERVAL_SECONDS = 15;

    /**
     * Heartbeat 스케줄러
     */
    private final ScheduledExecutorService heartbeatScheduler = Executors.newScheduledThreadPool(3);

    @PreDestroy
    public void destroy() {
        heartbeatScheduler.shutdown();
        try {
            if (!heartbeatScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                heartbeatScheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            heartbeatScheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
        log.info("Heartbeat 스케줄러 종료");
    }

참고

 

Spring graceful shutdown (+ async, virtual thread)

배포나 scale-in 등의 이유로 Spring 서버 종료 시, 해당 서버가 처리하고 있는 HTTP 요청은 어떻게 될까?요청 처리를 중단하고 서버가 종료되기 보다는 요청 처리를 다 마친 뒤에 서버가 종료되는 것

devs0n.tistory.com

 

[SpringBoot] 스프링 부트 톰캣 애플리케이션의 Graceful Shutdown 동작 방식(Spring Boot Tomcat Graceful Shutdown)

1. 스프링 부트 톰캣 애플리케이션의 Graceful Shutdown 동작 방식(Spring Boot Tomcat Graceful Shutdown)[ GracefulShutdown의 필요성 ]새로운 버전의 서버를 배포하기 위해서는 기존에 실행중인 서버를 종료하는 과

mangkyu.tistory.com

 

SmartLifecycle (Spring Framework 7.0.0 API)

Indicates that a Lifecycle component must stop if it is currently running. The provided callback is used by the LifecycleProcessor to support an ordered, and potentially concurrent, shutdown of all components having a common shutdown order value. The callb

docs.spring.io

https://medium.com/@AlexanderObregon/managing-lifecycle-hooks-with-smartlifecycle-in-spring-boot-a85d3ae70360

 

프랜차이즈연구소

데이터로 분석하는 똑똑한 프랜차이즈 창업, 프랜차이즈연구소

fchalab.com

 

댓글