πŸ“• Spring Framework

Spring Boot μ• ν”Œλ¦¬μΌ€μ΄μ…˜ k8s ν™˜κ²½μ—μ„œ WarmUp μ μš©ν•˜κΈ°

GroovyArea 2024. 3. 17. 14:29

이 글을 μž‘μ„±ν•˜λŠ” 이유

ν˜„μž¬ νšŒμ‚¬μ—μ„œλŠ” 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 기반 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— λ™μΌν•˜κ²Œ μ μš©ν•˜λŠ” μ–΄λ“œλ°”μ΄μŠ€λ₯Ό 쀄 수 μžˆμ„ 것 κ°™λ‹€!

 

 

λ°˜μ‘ν˜•