본문 바로가기
📕 Spring Framework

Spring Boot 애플리케이션 k8s 환경에서 WarmUp 적용하기

by SweeetGogum 2024. 3. 17.

이 글을 작성하는 이유

현재 회사에서는 EKS 기반 k8s 환경에서 spring boot 를 포함 각종 프레임워크 애플리케이션을 운영 중이다.

 

기존 모노리스 Django Rest Framework 에서 점진적으로

도메인 분리를 Spring Boot 를 이용한 Micro Service Application 으로 진행 중인데,

내가 맡은 결제 도메인 관련 애플리케이션도 마찬가지이다. (Kotlin 기반 Spring Boot App)

 

문제는, 결제가 주문 및 유관 DB 와 너무 강하게 얽혀 있어서 바라보아야 하는 테이블이 많다는 점이고,

이것은 곧 배포 직후 속도에 커다란 영향을 끼쳤다.

 

JVM 은 컴파일 된 .class 파일을 필요 시 클래스 로딩을 통해 사용하며,

기본적으로 인터프리터 방식을 사용하므로 Jit Compiler 를 사용하기까지 여러 번의 호출이 필요하다.

이 것이 근본적인 문제다.

 

내가 최근에 개발했던 API 는 결제 내역 적재 API 이다.

하나의 API 로 거진 20개 가량의 테이블들을 read, write 하는데, 배포 직후 테스트를 하면 항상 40 ~ 50 초 대 (1분 넘는 것도 있었음..) 를 기록했다. => 뭔가 이상하다 생각했다.

 

처음엔 내 코드를 의심했고, Data Dog Span 을 통해 확인해본 결과 문제는 다른 부분에 있었다.

(물론 내 코드 또한 처음 JVM 메모리에 로드 되는데 시간이 걸렸다.)

 

인덱스를 타는 단순 조회 쿼리나, 간단한 Insert 쿼리 실행 latency 가 비정상적으로 길다는 것을 확인했다.

특히 조회 쿼리의 경우, 애플리케이션 코드에 적용하기 전, 항상 실행 계획을 통해 확인 후 작성을 한다.

JPA 를 사용하므로, 어쩌면 캐싱이 되지 않은 데이터를 조회하기 위해 디스크에 직접적으로 IO 진행하므로, 이 부분에 대해서 문제를 파악하게 되었고, hibernate 코드 또한 처음 인터프리터로 실행 후 메모리에 로드 되므로, 한 몫을 하리라 생각했다.

 

"WarmUp" 이란 개념을 시니어 개발자 분께 들었고,

API Controller 코드 부터 진행하면 좋겠지만 필요한 샘플 데이터가 방대하므로,
Latency 가 가장 큰 DB IO 단 Repository 부분만 웜업을 적용하기로 했다.

 

WarmUp 를 적용하는 과정

기본적으로, Spring Boot 기반 Micro Service 를 운영 중이면,

Spring 에서 제공하는 Actuator 를 사용하고 있을 것이다.

액츄에이터는 애플리케이션의 모니터링과 매트릭 (서비스의 성능 측정에 사용되는 항목이나 지표) 같은 기능을 http or JMX 엔드포인트를 통해서 제공한다.

https://velog.io/@hope0206/Spring-boot-Actuator-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-%EC%95%A1%EC%B6%94%EC%97%90%EC%9D%B4%ED%84%B0-API-Spring-Cloud%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%88%EC%A0%9C-iwfhzo1h

 

📗 Spring boot Actuator 스프링 부트 액추에이터 API + Spring Cloud를 사용한 예제

Spring Actuator와 Spring Cloud의 이해 😗

velog.io

=> 자세한 건 이 블로그를 참조해보라~

 

우리 Spring Boot Application 의 경우 actuator 를 활용한 지표를 요청하기 위해 필요한 설정을 아래와 같이 설정했다.

management:
  endpoints:
    web:
      base-path: "/"

 

만약 health 상태를 확인하고 싶다면,

(도메인 호스트)/health 

-> 요런 식으로 요청하면 된다~

호출 결과

 

K8s 를 활용 중이라면,

아래와 같이 StartupProbe 를 통해 Health check 를 진행할 수 있다,

Probe 란 컨테이너에서 kubelet 에 의해 주기적으로 수행되는 진단인데,

여러 Probe 를 통해 각 컨테이너의 상태를 주기적으로 체크 후 컨테이너의 재시작이나 문제가 있을 경우 서비스에서 제외 시킬 수 있다.

이 중, StartUp Probe 의 경우, 컨테이너의 애플리케이션이 시작 되었는지 나타낸다.

성공할 때까지 나머지 Probe 는 활성화 되지 않고, 실패하면, kubelet 은 컨테이너를 죽이고,

재시작 정책에 따라 처리가 된다.

 

각 Probe 가 궁금하다면?

https://velog.io/@hoonki/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-Probe

 

쿠버네티스 - Probe (Liveness, Readiness, Startup)

오늘은 쿠버네티스의 Probe라는 개념에 대해 정리해보고자한다. 예전에 사내 dev 클러스터 구축 시 probe 라는 개념이 yaml에 적혀있어 이것이 무엇인지 궁금해서 찾아보았다. Probe란? Probe는 컨테이

velog.io

=> 여기를 참고하자~

 

우리 StartUp Probe 설정의 경우 아래와 같다. (실제 설정이 아닌 예시이다 ㅎㅎ)

          startupProbe:
            httpGet:
              path: /health/readiness
              port: http
            failureThreshold: 100
            periodSeconds: 5

 

5초마다 한번 씩 실행 되는 StartUp Probe 는 100 번의 실패 

즉, 500 초 동안 컨테이너가 시작 되는 동안 응답을 제대로 하지 않을 경우 실패한다.

 

이 엔드포인트에 웜업 코드를 적용할 것이다.

 

Warm Up 코드 적용

웜업을 적용하기 위해서는 Spring Framework 의 HealthIndicator 를 상속받아 구현해야 하는데, 

@Component
class WarmupHealthIndicator(
    private val warmer: Warmer,
) : AbstractHealthIndicator() {

    override fun doHealthCheck(builder: Health.Builder) {
        warmer.run()

        if (warmer.isDone) {
            builder.up()
        } else {
            builder.down()
        }
    }
}

abstract class ExactlyOnceRunWarmer : Warmer {

    override var isDone: Boolean = false
    private val mutex = Mutex()

    override fun run() {
        if (!isDone && mutex.tryLock()) {
            try {
                doRun()
                setDone()
            } finally {
                mutex.unlock()
            }
        }
    }

    protected fun setDone() {
        this.isDone = true
    }

    abstract fun doRun()
}

@Component
class PaymentCustomWarmer(
    private val paymentDataWarmer: PaymentDataWarmer,
) : ExactlyOnceRunWarmer() {

    override fun run() {
        paymentDataWarmer.dataWarmUp() // 실제 웜업이 필요한 코드를 작성했다.
    }
}

 

 

위와 같다. 

Exactly Once Warmer  부분의 경우,

Line 공식 기술 블로그를 차용했다. (감사합니다..)

https://engineering.linecorp.com/ko/blog/apply-warm-up-in-spring-boot-and-kubernetes

 

Spring Boot + Kubernetes 기반에서 웜업 적용하기

안녕하세요. LINE+ ABC Studio 팀에서 백엔드 개발을 하는 박원영, 박제희입니다. 현재 일본에서 운영하는 배달 서비스 '데마에칸(Demaecan, 出前館)' 프로덕트에서 점포 목록을 제공하는 서비스를 개발

engineering.linecorp.com

=> 궁금하다면 참고~

 

이런식으로 진행하면 끝이다~ 간단하지 않은가?

(웜업 코드 노가다는 말 안해도 아시죠..?)

 

실제로 배포를 진행하였고,

Start Up Probe 가 실행 되며 웜업을 적용한 쿼리가 실행 되는 것을 확인할 수 있었다.

쿼리 실행이 완료 되면 실제로 트래픽을 받을 수 있게 된다.

 

결과

DataDog 을 통해 Latency 를 비교해보았다.

=> 웜업 적용 전 첫 요청.. 처참..

=> 웜업 적용 후 첫 요청, 확연하게 차이가 나는 것을 확인할 수 있다.

 

결론

실제로 Warm up 을 진행하면서,

k8s 의 Probe 개념을 더 명확히 하게 됨과 동시에, JVM 이 왜 얼마나 느린지 체감할 수 있었다.

현재 프로젝트를 진행하면서, 난이도는 가장 높음과 동시에, 배우는 게 정말 많다는 것을 느낀다.

 

앞으로 더 결제 관련 API 가 분리될 예정인데,

리소스 관련해서 더 문제에 봉착하게 될 것 같고 (ㅎㅎ) 공부를 더 진행하게 될 것 같다.

 

최근 주말 내내 작업하며 어려웠지만, 

이번 기회에 훌륭한 자산을 얻게 되었고, 차후 동일한 문제에 직면한 JVM 기반 애플리케이션에 동일하게 적용하는 어드바이스를 줄 수 있을 것 같다!

 

 

반응형

'📕 Spring Framework' 카테고리의 다른 글

Spring Cloud OpenFeign 더 잘 사용해보기  (0) 2024.05.11