개발 일기

Graceful Shutdown 이해하고 적용하기 본문

Java&Spring/Spring

Graceful Shutdown 이해하고 적용하기

flow123 2024. 5. 27. 09:26

Graceful Shutdown 이란?

 

스프링 부트의 공식문서에서는 아래와 같이 설명한다. 해석해보면 이런 뜻이다.

 

GracefulShutdown 은 애플리케이션 컨텍스트를 종료하고, 빈을 중단하는 초기 단계에서 일어난다.

중단하는 과정에서는 Grace Period 를 제공하는 타임아웃을 사용한다. Grace Period 동안 기존 요청들은 실행을 마칠 수 있지만, 새로운 요청은 허용되지 않는다.

 

It occurs as part of closing the application context and is performed in the earliest phase of stopping SmartLifecycle beans. This stop processing uses a timeout which provides a grace period during which existing requests will be allowed to complete but no new requests will be permitted.

 

스프링 부트의 기본 WAS 톰캣에서 처리하는 방식을 보자 (아래 공식 문서 참고) 

톰캣에서는 shutDownGracefully 메서드를 통해서 Graceful Shutdown 이 시작된다. 새로운 요청은 네트워크 레벨에서 막기 시작하며 (기존에 남아있던 커넥션에 들어오는 요청 포함), Shutdown 이 완료 되면 애플리케이션이 알 수 있도록  CallBack 이 호출된다.

 

public void shutDownGracefully(GracefulShutdownCallback callback)

Initiates a graceful shutdown of the Tomcat web server. Handling of new requests is prevented and the given callback is invoked at the end of the attempt. The attempt can be explicitly ended by invoking stop(). Once shutdown has been initiated Tomcat will reject any new connections

적용

 

application.yml  에 다음 설정을 추가한다.

현재 사용 중인 스프링 부트 2.7.7 의 ShutDown-phase 의 기본값은 30초이다.

이렇게 시간을 설정해주면, 데드락처럼 서로를 무한으로 대기하는 상황을 막아준다.

 

server:
    shutdown: graceful

# 타임아웃 시간 설정 
spring
	lifecycle:
		timeout-per-shutdown-phase: 10s

테스트

import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

private val log = KotlinLogging.logger {}

@RestController
class TestController {
    @GetMapping("/sleep")
    fun sleep(@RequestParam millis: Long) {
        try {
            log.info { "[Started] sleeping for " + millis + "millisec" }
            Thread.sleep(millis)
        } catch (e: InterruptedException) {
            Thread.currentThread().interrupt()
            log.info { "Interrupted during sleep" }
        }
        log.info { "[Completed] slept for " + "$millis" + "millisec" }
    }
}

프로세스 종료

 

- Kill - 9 (SIGKILL) {PID} 프로세스를 강제로 종료한다.
- Kill - 15(SIGTERM) {PID} 프로세스를 Graceful 하게 종료한다. 종료하기 전에 리소스를 정리한다.

 

 PID 를 찾을 때는, 

- lsof -i :{port 번호} :


graceful shutdown 적용 전

 

PID 확인:  lsof -i :8080

10초가 지나기 전에 SIGKILL 로 종료해보자. 작업을 마무리하지 못한채 종료된다. 

 

SIGTERM 으로 종료

 

Exception 발생이 추가되긴 하지만, 역시 작업을 중단하고 종료가 된다.

graceful shutdown 적용 후

 

10초 동안 sleep 을 걸었는데, 만약 10초의 작업이 끝나기 전에 프로세스 종료 요청이 온다면 어떻게 될까? (

요청이 종료된 이후, Shutdown을 완료한다.

 

 

위의 결과는 sleep 시간보다 shutdown phase가 길거나 같을 때에 해당한다.

10초동안 Sleep 을 걸었는데, shutdown phase 가 그보다 짧은 5초라면 어떨까? 


5초 간의 타임아웃 이후에도 여전히 bean 이 살아있다. 따라서 하나 이상의 요청이 살아있기 때문에, Graceful shutdown 이 중단된다. 즉, 살아있는 요청을 완료하는 걸 우선시하기 때문에, 데이터 손실이나 정합성이 깨지는 것을 막을 때 유리하다. 

출처

https://docs.spring.io/spring-boot/reference/web/graceful-shutdown.html

https://docs.spring.io/spring-boot/api/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.html

https://medium.com/@arneg0shua/파일-업로드-도중-배포를-진행해도-괜찮을까-graceful-shutdown-적용편-453ae9f29dd1

https://velog.io/@dongvelop/Springboot-Graceful-Shutdown

 

'Java&Spring > Spring' 카테고리의 다른 글

노동요 공유 커뮤니티 프로젝트 시작!  (0) 2022.01.24
Comments