Spring Boot 환경에서 application을 shutdown 하는 방법 중 대표적인 것이 actuator의 shutdown endpoint 기능을 이용하는 것입니다. 이 endpoint는 예상과 달리 처리 중인 요청이 있더라도 그냥 shutdown 처리를 합니다. 정말로 그런지 확인해보겠습니다.

graceful 하지 않는 shutdown

먼저 actuator shutdown endpoint를 사용하려면 설정을 통해서 활성화시켜야 합니다.

# actuator
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"

shutdown endpoint가 외부로 공개되면 누구나 실행할 수 있기 때문에 권한 설정이나 내부에서만 실행 가능한 구조로 만들어야 합니다

위와 같이 application.yml에 설정을 추가하고

shutdown endpoint를 호출하면 “Shutting down, bye…“라는 응답과 함께 application도 종료된 것을 확인할 수 있습니다.

POST http://localhost:8888/actuator/shutdown

HTTP/1.1 200 
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Sun, 05 Apr 2020 01:52:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "message": "Shutting down, bye..."
}

그렇다면 요청받은 내용을 좀 오랫동안 처리 중인 스레드가 있을 경우에 /actuator/shutdown을 호출하면 어떻게 될까요?

먼저 아래와 같이 Controller를 만들고

@Slf4j
@RestController
public class ShutdownRestController {

    @GetMapping(value = "/long-process")
    public void process(@RequestParam(value = "sec") int sec) {
        for (; sec > 0; sec--) {
            log.info("{}", sec);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ignored) {
            }
        }
    }
}

30초간 대기하도록 호출한 다음에

GET http://localhost:8080/long-process?sec=30

shutdown을 호출하면

POST http://localhost:8888/actuator/shutdown

좀 전과 동일하게 “Shutting down, bye…” 메시지와 함께 application이 즉시(0.5초 후에) 종료됩니다.

{
  "message": "Shutting down, bye..."
}

예상한 기대 동작은 /actuator/shutdown을 호출한 이후에 새로운 요청은 더 이상 처리하지 않지만 내부적으로 처리 중인 작업은 완료하고 종료될 것이라 생각했지만 실제로는 그렇지 않습니다.

Marcos Barbero’s Blog 블로그 내용을 확인해 보면 tomcat 환경에서 graceful shutdown 하는 방법에 대해서 소개하고 있습니다. 이 블로그에 나와 있는 내용대로 GracefulShutdown 클래스를 만들고 설정한 후 /actuator/shutdown을 호출하면 지정한 시간만큼 대기한 후에 application이 종료되는 것을 확인할 수 있습니다.

Spring Reactive 환경에서 Shutdown

위에서 살펴본 블로그 예제는 Tomcat 환경을 기준으로 설명되어 있습니다.

Spring Reactive 환경이라면 기본으로 Netty를 서버로 사용합니다. Netty일 경우에도 /actuator/shutdown를 호출하면 graceful shutdown 동작을 하지 않습니다. 해결 방법이 있을 것 같아 관련해서 구글 검색해봐도 자료가 많지도 않고 그나마 있는 것도 기대처럼 동작하지 않았습니다.

Spring Boot 2.3.0 Graceful shutdown

Netty 환경에 대해서 직접 구현하기에는 이해하기 부족했기 때문에 좀 더 확인한 결과 Spring Boot 2.3.0에서 graceful shutdown을 지원한다고 합니다.

2020-04-07 기준으로 아직 M4 버전입니다. M3 버전의 Release Notes를 보면 Graceful shutdown 기능이 추가된 것을 확인할 수 있습니다.

server.shutdown.grace-period 옵션을 통해서 대기 시간을 설정할 수 있습니다.

server:  
  port: 8080  
  shutdown:  
    grace-period: 60000		# 최대 60초 대기

위와 같이 설정하고 아래와 같이 webClient로 40초 이상 소용되는 작업을 비동기 호출 하고 /actuator/shutdown을 호출하면 해당 작업이 완료된 이후에 서버가 종료되는 것을 확인할 수 있습니다.

@Configuration
public class ShutdownRouter {

    @Bean
    public RouterFunction<ServerResponse> composedRoutes() {
        return route(GET("/long-process"), request -> {
            // 40초 소요되는 작업 호출
            Mono<String> result = webClient.get()
                    .uri("http://localhost:8888/long-process?sec=40")
                    .retrieve()
                    .bodyToMono(String.class);
            return ok().body(result, String.class);
        });
    }
}

보통 API에서 요청을 처리할 때는 길어도 수 초내에 처리를 끝나서 문제가 없겠지만 예외적으로 길게 처리하더라도 gracful shutdown 최대 대기 시간을 넘지 않도록 하는 게 좋을 것 같습니다.

끝.

Reference

태그:

카테고리:

업데이트:

댓글남기기