Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/content/blog/Programming/26.10.14 Spring Boot Graceful Shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: "Spring Boot Graceful Shutdown: 진행 중인 요청을 마무리하고 종료하기"
description: "graceful 종료와 강제 종료의 차이, 종료 단계별 제한 시간, SIGTERM과 SIGKILL, 배포 시 확인할 작업의 범위를 정리합니다."
pubDate: "2026-10-24T00:00:00+09:00"
categories: "Programming"
tags: ["Java", "Spring Boot", "Deployment", "Graceful Shutdown"]
slug: "programming/spring-boot-graceful-shutdown"
---

## 핵심 요약

- Graceful Shutdown은 신규 요청 처리를 제한하고, 진행 중인 요청에 완료할 시간을 주는 종료 방식입니다.
- Spring Boot 3.4부터 내장 웹 서버의 graceful 종료가 기본으로 활성화되었습니다.
- HTTP 요청의 종료 유예와 백그라운드 작업, 메시지 소비자, 배포 플랫폼의 종료 제한 시간은 함께 확인해야 합니다.

## 결제 요청을 처리하는 중에 서버를 종료한다면

서버가 결제 요청을 처리하는 중에 프로세스를 강제로 종료하면 응답이 끊길 수 있습니다. DB 변경과 외부 결제 승인처럼 여러 시스템을 거치는 작업이라면, 어느 단계까지 처리되었는지도 따로 확인해야 합니다.

Graceful Shutdown은 이러한 중단을 줄이기 위해 진행 중인 요청에 완료할 기회를 줍니다. 요청의 성공이나 데이터 정합성을 모두 보장하는 기능은 아닙니다. 외부 시스템의 상태 확인, 트랜잭션 설계, 재시도와 중복 처리 정책은 여전히 필요합니다.

| 구분 | 즉시·강제 종료 | Graceful Shutdown |
| --- | --- | --- |
| 진행 중인 요청 | 중단될 수 있음 | 제한 시간 안에 완료할 기회 제공 |
| 신규 요청 | 프로세스 종료로 처리 불가 | 서버 구현에 따라 접수 중단 또는 거절 |
| 종료 시간 | 짧지만 정리 기회가 부족할 수 있음 | 설정된 종료 정책에 따라 대기 |
| 작업 성공 보장 | 없음 | 없음 |

## Spring Boot 설정

다음은 Spring Boot 3.4의 내장 웹 서버를 기준으로 한 설정입니다. 3.4부터 기본 활성화되었지만, 프로젝트의 의도를 드러내기 위해 명시할 수도 있습니다.[1][2]

```yaml
server:
shutdown: graceful

spring:
lifecycle:
timeout-per-shutdown-phase: 30s
```

`server.shutdown`은 웹 서버의 종료 방식을 지정합니다. `spring.lifecycle.timeout-per-shutdown-phase`는 종료 단계별 유예 시간을 설정합니다.

여기서 **30초는 프로세스 전체가 반드시 30초 이내에 끝난다는 뜻이 아닙니다.** 여러 생명주기 단계와 다른 종료 콜백이 있으므로, 애플리케이션 전체 종료 시간과 구분해야 합니다. 유예 시간이 지나도 요청이 계속 완료될 것이라고 가정해서는 안 됩니다.

종료 처리는 ApplicationContext를 닫는 과정에 포함됩니다. 그 안에서 웹 요청을 정리하고, 생명주기 빈과 리소스도 각자의 종료 계약에 따라 정리합니다.[1]

## 신규 요청을 모두 같은 방식으로 막을까?

Spring Boot 3.4 문서에서 Tomcat, Jetty, Reactor Netty는 네트워크 계층에서 신규 요청 접수를 중단합니다. Undertow는 새 연결을 받되 503 응답을 반환하는 방식입니다.[1]

따라서 “모든 웹 서버가 새 연결을 똑같이 끊는다”라고 설명하기보다는, **신규 요청 처리를 제한하는 구체적인 방식은 서버 구현에 달려 있다**고 이해하는 것이 정확합니다. 내장 서버를 쓰는지, 외부 서블릿 컨테이너에 배포하는지도 먼저 확인해야 합니다.

## SIGTERM과 SIGKILL의 차이

Linux에서 실행한 애플리케이션에 정상 종료를 요청할 때는 SIGTERM을 사용할 수 있습니다. 아래 PID는 실제 애플리케이션의 PID로 바꿔야 합니다.

```bash
APP_PID=12345
kill -TERM "$APP_PID"
```

기본 종료 훅이 등록된 Spring Boot 애플리케이션이라면 JVM 종료 과정에서 컨텍스트를 닫고 graceful 종료를 수행할 수 있습니다. 반면 SIGKILL은 프로세스가 처리할 수 없는 강제 종료 신호입니다.

```bash
kill -KILL "$APP_PID"
```

`kill -15`는 SIGTERM, `kill -9`는 SIGKILL을 숫자로 표현한 형태입니다.[3] 전원 차단이나 SIGKILL에서는 정리 콜백이 실행될 것을 기대할 수 없습니다. IDE의 Stop도 정상적인 종료 신호를 보내는지에 따라 결과가 달라질 수 있습니다.[1]

## 개발 환경에서 확인하기

운영 요청 대신 개발용 환경에서, 몇 초 걸리는 테스트 요청으로 확인합니다.

1. 5초 정도 걸리는 요청을 시작합니다.
2. 요청이 진행 중일 때 SIGTERM으로 종료를 요청합니다.
3. 요청의 응답과 서버의 종료 로그를 함께 확인합니다.
4. 별도 실행에서 유예 시간보다 긴 요청도 시험해 제한 시간 이후 동작을 확인합니다.

요청을 보내기 전에 서버를 종료해 버리면 진행 중인 요청의 완료를 검증한 것이 아닙니다. 요청이 실제로 시작되었는지 로그나 테스트용 동기화 지점으로 확인하는 것이 좋습니다. 프록시·클라이언트의 타임아웃도 결과에 영향을 줍니다.

## 무중단 배포에는 무엇이 더 필요할까?

Graceful Shutdown만 설정해도 무중단 배포가 완성되는 것은 아닙니다.

| 확인 대상 | 확인할 내용 |
| --- | --- |
| 새 인스턴스 | 실제 요청을 받을 준비가 되었는지 |
| 로드밸런서 | 종료할 인스턴스로 새 트래픽이 전달되지 않는지 |
| 기존 요청 | 애플리케이션·프록시·클라이언트의 타임아웃이 맞는지 |
| 실행 플랫폼 | 유예 시간 전에 프로세스를 강제 종료하지 않는지 |
| 백그라운드 작업 | 스케줄러·비동기 실행기·메시지 소비자의 종료 정책 |

HTTP 응답을 반환한 뒤 별도 스레드에서 계속되는 작업은 HTTP 종료 유예만으로 완료가 보장되지 않습니다. 메시지 처리라면 승인 시점과 재전달, DB 작업이라면 커밋·롤백과 자원 정리도 확인해야 합니다.

종료할 인스턴스의 트래픽을 정리하고, 진행 중인 작업이 끝날 시간을 확보하며, 마지막 강제 종료 제한까지 맞추는 것이 실제 운영에서의 과제입니다.

## 참고 자료

1. [Spring Boot 3.4 — Graceful Shutdown](https://docs.spring.io/spring-boot/3.4/reference/web/graceful-shutdown.html)
2. [Spring Boot 3.4 Release Notes — Graceful Shutdown](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.4-Release-Notes#graceful-shutdown)
3. [Linux man-pages — signal(7)](https://man7.org/linux/man-pages/man7/signal.7.html)
Loading