@Component
public class WarmUpRunner implements ApplicationRunner {
private final BoardService boardService;
private final CategoryService categoryService;
// 서비스 추가 시 매번 필드 + 호출 추가 필요
@Override
public void run(ApplicationArguments args) {
boardService.warmupBoardData();
categoryService.warmupCategoryData();
}
}
웜업 작업의 수동 관리가 필요한것입니다. 웜업 수동 방식은 다음과 같은 문제를 가집니다.
코드변경없이 @Warmup어노테이션 하나만 붙여서 자동으로 실행되도록
실행 순서도 지정 가능하게
어노테이션에 선언적으로 메타정보만 부여하고, 나머지 로직은 리플렉션이 처리
유지보수의 비효율 특정로직을 수정하면 웜업서비스에서도 코드수정이 필요하다. 이는 유지보수가 어렵고 관리포인트를 늘린다.
“B가 A를 사용하는 경우, A를 먼저 웜업해야 하지 않을까?”
예를 들어, UserService가 내부적으로 api호출을 한다면,
UserService가 먼저 웜업하고 그다음 UserService를 실행하는 것이 더 효율적이다.
의존성 순서에 따라 웜업이 실행되도록 순서를 조절할 수 있어야 한다.
| 방법 | 장점 | 단점 |
|---|---|---|
| AOP | 실행 감지에는 좋음 | 실행 트리거에는 부적합 |
| 컴파일 타임 APT | 성능 좋음 | 웜업은 런타임에 실행돼야 하므로 부적절 |
| 리플렉션 + 어노테이션 | 실행 자동화, 선언적 구성 | 리플렉션 비용은 있지만 초기 1회이므로 감내 가능 |
결국, 런타임에서 자동으로 웜업 메서드를 찾아 실행할 수 있는 리플렉션 기반 설계로 확정했습니다.
@Repeatable(WarmupContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Warmup {
boolean enabled() default true;
WarmupType[] type() default { WarmupType.DEFAULT };
int order() default 1; // 의존성 순서 조절용
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
if (!warmupEnabled) return;
List<WarmupMethod> warmupMethods = new ArrayList<>();
for (String beanName : context.getBeanDefinitionNames()) {
Object bean = context.getBean(beanName);
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
Warmup warmup = method.getAnnotation(Warmup.class);
if (warmup == null || !warmup.enabled() || method.getParameterCount() > 0) continue;
warmupMethods.add(new WarmupMethod(bean, method, warmup.order()));
}
}
warmupMethods.stream()
.sorted(Comparator.comparingInt(WarmupMethod::order)) // 순서대로 실행
.forEach(warmupMethod -> {
try {
warmupMethod.method().setAccessible(true);
warmupMethod.method().invoke(warmupMethod.bean());
log.info("✅ [Warmup 완료] {}.{}", warmupMethod.bean().getClass().getSimpleName(), warmupMethod.method().getName());
} catch (Exception e) {
log.warn("❌ [Warmup 실패] {}.{}", warmupMethod.bean().getClass().getSimpleName(), warmupMethod.method().getName(), e);
}
});
warmupState.markComplete();
}
https://github.com/cwangg897/warm-up
https://oliveyoung.tech/2024-10-30/application_warmup_algorithm/