Spring 컨텍스트의 생명 주기(start / stop)에 통합되어, 빈의 시작 및 종료 순서를 결정론적으로(Deterministic) 제어할 수 있게 해주는 인터페이스.
애플리케이션 종료 시점에 리소스 반납 및 진행 중인 트랜잭션/재시도의 중단을 순서대로 조율하여 우아한 종료(Graceful Shutdown)를 돕는다.
Standard (공식 정의)
Spring Framework 공식 명세에 따른 정의는 다음과 같다.
SmartLifecycle: Lifecycle 및 Phased 인터페이스의 확장판.
isAutoStartup(): 컨텍스트 리프레시 시점에 start()가 자동으로 실행될지 여부를 결정한다.
getPhase(): 생명 주기 상의 실행 단계를 나타낸다.
시작(Start) 순서: getPhase()가 작은 순에서 큰 순으로 기동된다.
종료(Stop) 순서: getPhase()가 큰 순에서 작은 순(내림차순)으로 정지된다.
따라서, phase가 Integer.MAX_VALUE인 빈은 가장 마지막에 기동되고, 종료 시점에는 가장 먼저 멈춘다.
한계 / 주의점
ContextClosedEvent 와의 차이: Spring의 ContextClosedEvent 리스너는 애플리케이션 컨텍스트가 닫히기 시작했다는 신호만 전달할 뿐, 빈의 소멸(destroy) 순서와 비결정론적으로 얽혀 있다. 예컨대 어떤 DB 소스 빈이 이미 소멸된 후에 커넥션을 수립하려는 리스너 코드가 호출되면 NPE나 의존성 부재 예외가 터진다.
SmartLifecycle은 비동기 셧다운을 차단할 수 있다: stop(Runnable callback) 메서드가 호출되면 종료 작업을 수행하고 반드시 callback을 호출해 주어야 한다. 그렇지 않으면 Spring이 설정된 셧다운 타임아웃까지 대기하여 기동 종료 과정이 지연될 수 있다.
이로 인해 Spring 컨텍스트가 종료 과정을 개시할 때, 다른 어떤 데이터베이스 빈이나 아웃바운드 의존성 어댑터가 종료되기 전에 가장 먼저 셧다운 가드의 stop()이 실행되어 shuttingDown 플래그를 세우게 됨.
리트라이 정책(OutboundRetryPolicy)은 루프 도중 이 플래그를 관찰하여 즉시 중단(short-circuit)하며, 신규 요청 또한 OutboundHttpClient 단에서 즉시 거부(DEPENDENCY_CIRCUIT_OPEN 예외)함으로써, 애플리케이션 종료 시 불필요한 HTTP 커넥션 맺기나 타임아웃 예산 낭비를 미연에 방지함.
왜 Graceful Shutdown 구현 시 ContextClosedEvent 리스너를 사용하는 대신 SmartLifecycle phase를 활용하는 것이 안전한가?
getPhase() 반환값이 Integer.MAX_VALUE일 때, 종료 시점의 제어 순서는 어떻게 보장되는가?
Interview Questions
Spring Framework에서 애플리케이션이 안전하게 종료(Graceful Shutdown)되도록 빈의 소멸 순서를 조율하는 방법에 대해 설명하고, SmartLifecycle 인터페이스의 동작 방식을 설명하십시오.
Kubernetes 환경에서 Pod가 종료 신호(SIGTERM)를 받았을 때 Spring Boot 애플리케이션이 수신 중인 API 및 아웃바운드 재시도 요청을 처리하는 우아한 종료 흐름을 설계해 보십시오.
Do Not Overclaim
"SmartLifecycle을 적용했기 때문에 종료 과정에서 어떠한 데이터 유실도 물리적으로 발생하지 않는다"고 보장해서는 안 된다. 컨테이너 셧다운 유예 기간(Kubernetes terminationGracePeriodSeconds)을 넘어가면 강제 종료(SIGKILL)가 발생하므로, 애플리케이션의 우아한 정리 시간이 유예 기간보다 짧도록 세심히 설정해야만 보장된다.