운영 배포를 자주 하다 보면, 배포 후 사이트에 접속했을 때 첫 데이터가 늦게 나오는 현상이 종종 있었다.
"문제가 있나?" 싶은 조바심이 생기고, 특히 K8s 스케일아웃 환경에서는
구글 연구(2018)에 따르면, 페이지 로딩 시간이 1초에서 5초로 늘어나면 이탈률이 90% 이상 증가한다.
첫 요청 시 느려지는 원인을 분석한 결과, 콜드 스타트 현상 때문이다
클래스는 실제 호출될 때 JVM이 메모리에 로드함.
따라서 최초 요청 시 Controller, DTO, Entity 등이 늦게 로딩됨 → 지연 발생.
기본은 Eager 초기화지만, @Lazy 또는 spring.main.lazy-initialization=true 설정 시 요청이 올 때까지 초기화를 미룸.
JPA EntityManager, Redis, Kafka, 외부 API 등은 요청 시점까지 연결이 지연됨.
서버가 "띄워졌다"와 "요청을 받아도 되는 상태"는 다르다.
Kubernetes의 StartupProbe를 활용해 "진짜 준비된 서버"만 트래픽을 받게 한다.
[Spring Boot 시작]
↓
[ApplicationReadyEvent 발생]
↓
[Warmup 실행]
↓
[모든 웜업 성공 → /health/startup = 200 OK]
↓
[Kubernetes startupProbe 통과 → 트래픽 수신 시작]
JVM의 Tiered Compilation이 기본 활성화되어 있기 때문이다.
JVM은 Tiered Compilation을 사용하여 아래 표대로 1단계씩 점진적으로 최적화한다.
| Tier 단계 | 컴파일러 | 특징 |
|---|---|---|
| Tier 0 | 인터프리터 | 느리지만 빠른 시작 가능 |
| Tier 1 | C1 컴파일러 | 기본적인 최적화 적용 |
| Tier 2~3 | C1 + Profile | 히트 정보 수집 |
| Tier 4 | C2 컴파일러 | 강력한 최적화 (JIT 핵심) |
즉, CompileThreshold까지 가지 않더라도, Tier 1~3 단계에서 이미 성능 최적화가 일부 이뤄진다.
웜업이 되었다는 건 단순히 "1번 호출"이 아니라,
CompileThreshold는 우리가 수치로 직접 설정 가능하지만 Tiered Compilation은 아래 명령어를 통해 언제 어떤 단계로 올라갔는지 알 수 있다
java -XX:+PrintCompilation -jar myapp.jar
| 항목 | CommandLineRunner | ApplicationReadyEvent |
|---|---|---|
| 실행 시점 | Spring Context 초기화 직후 | 애플리케이션이 완전히 실행된 후 (@EventListener) |
| 빈 초기화 여부 | 대부분 완료된 상태 | 모든 Runner, @PostConstruct, CommandLineRunner 실행 후 |
| 실행 순서 제어 | @Order 또는 Ordered 인터페이스로 지정 가능 | 이벤트 리스너 간의 우선순위는 지정하기 어려움 |
| 용도 | 초기 데이터 로딩, 인자 기반 처리, 테스트용 | 서버 포트 오픈 이후 수행할 로직 (ex. 알림, 웜업 등) |
| 애플리케이션 준비 상태 | 아직 WebServer가 완전히 리스닝하지 않을 수도 있음 | 완전히 준비됨 (트래픽 받을 준비 완료) |
Spring Boot + Kubernetes 환경에서의 콜드 스타트 문제는 단순히 "배포 완료"가 아니라 "처리 준비 완료" 여부를 확인하는 것으로 해결 가능하다.
이를 위해 @Warmup 기반 로직과 startupProbe를 연동한 시스템을 구축했고, 초기 응답 속도를 60% 이상 개선했다.