Spring Scheduler 실행 지연 완벽 분석: 밀림 현상 해결을 위한 5가지 체크리스트
Spring Scheduler를 운영하다 보면 "10분마다 돌아야 할 스케줄러가 왜 12분, 15분 뒤에 돌지?" 하는 의문이 들 때가 있습니다. 단순히 '스케줄러가 늦게 돈다'고 생각하고 코드의 주기 설정만 바꾸면 진짜 원인을 찾기 어렵습니다.
결론부터 말씀드리면, @Scheduled 어노테이션 자체의 문제라기보다는 실제 작업의 소요 시간, 스레드 설정, DB 응답 속도, 외부 API 지연, 서버 리소스 부족이 얽혀 있는 경우가 대부분입니다. 실행 시간이 밀릴 때 정확한 원인을 파악하고 해결하는 방법을 실전 기준으로 정리해 드립니다.
스케줄러 지연 시 자주 나타나는 증상
현업에서 스케줄러 밀림 문제가 발생할 때 보통 다음과 같은 현상들이 관찰됩니다.
- 10분 주기로 설정했으나 12분, 15분 등 불규칙하게 지연되어 실행됨
- 로그 확인 시 이전 작업의 종료 시간이 예상보다 늦어짐
- 사용자가 몰리는 특정 시간대에만 스케줄러 시작이 지연됨
- DB 조회나 외부 API 호출이 많은 특정 배치 작업에서 유독 느려짐
- Docker 컨테이너 환경에서 배포했을 때 밀림 현상이 더 잦아짐
이러한 문제들은 스케줄러의 엔진 문제라기보다는 스케줄러 안에서 실행하는 비즈니스 로직이 원인인 경우가 많습니다.
왜 이런 문제가 발생할까?
1) 작업 시간이 스케줄 주기보다 길어지는 경우 (가장 흔함)
가장 빈번하게 발생하는 원인입니다. 10분마다 실행되는 작업의 실제 로직 처리 시간이 11분이 걸린다면, 다음 실행은 당연히 밀릴 수밖에 없습니다. 대량의 DB 조회, 무거운 파일 처리, 외부 API 호출 대기 등이 포함되어 있다면 실행 시간은 유동적으로 변하게 됩니다.
2) 스케줄러 스레드가 부족한 경우
Spring Scheduler는 기본적으로 스레드 풀 크기가 1인 단일 스레드(Single Thread)로 동작합니다(별도의 ThreadPoolTaskScheduler 설정을 하지 않았다면).
즉, A 스케줄러 작업이 오래 걸리면 완전히 무관한 B 스케줄러 작업도 실행되지 못하고 대기해야 하는 구조가 될 수 있습니다.
실행 시간이 밀릴 때 확인해야 할 5단계 체크리스트
1단계. 정확한 소요 시간(Elapsed Time) 로그 남기기
가장 먼저 해야 할 일은 '스케줄러가 늦게 켜진 것인지' 아니면 '로직이 오래 걸린 것인지'를 분리하는 것입니다. 코드에 실행 소요 시간 로그를 추가하세요.
long start = System.currentTimeMillis();
try {
log.info("scheduler start");
// 핵심 비즈니스 로직 실행
} finally {
long end = System.currentTimeMillis();
log.info("scheduler end. elapsed={}ms", end - start);
}
- 실행 시간(elapsed)이 계속 늘어난다면: 내부 로직(DB 쿼리, API 호출 등)에 병목이 생긴 것입니다.
- 실행 시간은 짧은데 시작 시점 자체가 밀렸다면: 스레드 고갈이나 서버 리소스 상태를 점검해야 합니다.
2단계. fixedRate vs fixedDelay 설정 점검
스케줄러의 실행 기준이 현재 비즈니스 로직 특성과 맞는지 확인해야 합니다. 작업 시간이 일정하지 않다면 fixedDelay가 훨씬 안전합니다.
| 옵션 | 동작 방식 | 추천 상황 |
| fixedRate | 이전 작업의 종료 여부와 무관하게 정해진 시간 간격마다 실행 시도 | 작업 시간이 매우 짧고 일정하며, 정확한 시간 간격이 중요할 때 |
| fixedDelay | 이전 작업이 완전히 종료된 후 지정된 시간이 지난 뒤 다음 작업 실행 | 작업 소요 시간이 들쭉날쭉하거나, 이전 작업과 절대 겹치면 안 될 때 |
3단계. DB 쿼리 및 트랜잭션 점검
로그 확인 결과 로직 소요 시간이 길다면, 주범은 높은 확률로 데이터베이스입니다.
- 한 번에 조회해 오는 데이터의 양이 너무 방대하지 않은가?
- 인덱스(Index)를 제대로 타지 않는 조건으로 쿼리하고 있는가?
- @Transactional 범위가 불필요하게 넓게 잡혀 있어 커넥션을 오래 쥐고 있지 않은가?
- DB 커넥션 풀(Connection Pool)이 부족하여 커넥션 획득을 기다리고 있지 않은가?
이 경우 스케줄러 설정이 아니라 쿼리 실행 계획(Explain)이나 슬로우 쿼리 로그를 확인하는 것이 먼저입니다.
4단계. 외부 API 응답 지연 구간 분리
로직 내에 외부 API 연동, SSH 통신, 외부 스토리지 작업 등이 있다면 응답 시간이 보장되지 않습니다. 전체 실행 시간만 보면 어디서 느려졌는지 알 수 없으므로 구간별 로그를 남겨야 합니다.
log.info("external API call start");
// 외부 API 호출 로직
log.info("external API call end");
특정 API에서 오랜 시간 멈춰있다면 무한 대기를 막기 위해 Timeout(Connect/Read) 설정과 예외 발생 시 재시도 정책을 튜닝해야 합니다.
5단계. Docker 또는 서버 리소스(CPU/Memory) 상태 확인
애플리케이션 코드가 문제없어도 서버 인프라가 버티지 못하면 느려집니다. 특히 Docker나 Kubernetes 컨테이너 환경이라면 할당된 리소스 한계를 확인하세요.
- CPU 사용률이 100%에 근접해 스레드 처리가 밀리고 있지는 않은가?
- 메모리가 부족해 잦은 GC(Garbage Collection)가 발생하며 Java 프로세스가 멈추고 있지는 않은가?
- 동일한 서버 내의 다른 무거운 작업이 리소스를 독점하고 있지는 않은가?
실전에서 가장 많이 하는 실수
"지연되니까 스케줄 주기를 늘려버리자"
스케줄러가 밀린다고 임시방편으로 스케줄 주기를 변경하는 경우가 많습니다. 이는 근본적인 원인(느린 쿼리, 리소스 누수 등)을 방치하는 것이므로 시스템 성능을 점점 갉아먹게 됩니다. "언제 시작했고, 언제 끝났고, 어느 구간에서 오래 걸렸는지" 파악하는 것이 최우선입니다.
"예외 처리 누락으로 인한 리소스 반환 실패"
작업 중 Exception이 발생했을 때 자원(커넥션, 파일 스트림 등)을 제대로 반환하지 않고 종료되면, 다음 스케줄러 실행 시 치명적인 리소스 고갈로 이어져 결국 스케줄러 전체가 멈추거나 심하게 지연됩니다.
마무리
Spring Scheduler의 실행 시간이 밀릴 때는 스케줄러 설정만 들여다보지 말고 작업의 전체 흐름을 관망해야 합니다.
당장 어디서부터 손대야 할지 모르겠다면 1단계에서 언급한 실행 소요 시간(Elapsed Time) 로그부터 추가해 보세요. 이 단순한 로그 하나만으로도 문제의 원인이 로직 내부에 있는지, 외부 인프라에 있는지 범위를 크게 좁힐 수 있습니다. 이후 DB, 외부 API, 서버 리소스 순으로 차근차근 점검해 나가면 안정적인 스케줄러 환경을 구축하실 수 있을 것입니다.
'IT > java' 카테고리의 다른 글
| Spring Scheduler 로그 관리 구조화 가이드 (0) | 2026.06.03 |
|---|---|
| Spring Scheduler 중복 실행 해결 가이드 (0) | 2026.05.26 |
| Docker Health Check 기반으로 Thread.sleep 제거하기 (0) | 2026.03.06 |
| Java로 SSH 접속, Docker 제어, GitHub Actions 트리거 구현기 (0) | 2026.02.26 |
| 원인을 찾지 못한 트랜잭션 에러, HikariCP를 의심하게 되기까지의 기록 (0) | 2026.01.30 |