글을 작성하게 된 계기
스케줄러/배치에서 실행 주기가 길 경우, 해당 Job이 실패했을 때, 잘 처리할 수 있는 방법을 고민하게 되었고, 이를 정리하기 위해 글을 작성하게 되었습니다. 이 부분은 정답이 없다고 생각하는데요, 저렇게 설계할 수도 있구나 정도로 생각해주시면 좋을 것 같습니다.
1. 자동/수동 실행 1:1 대응
스케줄러나 배치 작업을 설계할 때, 각 Job에 대해 이를 실행할 수 있는 수단을 1:1로 대응 시켜 제공하면 운영 측면에서 굉장히 편리합니다. 일반적으로 스케줄러/배치 작업은 정해진 시간 에 실행되도록 구성하는데, 이러한 정기적인 스케줄링 만으로는 운영이나 개발 관점에서 유연하게 대응하기 어렵기 때문입니다.
1
2
3
4
5
6
+---------------+ +--------------+ +---------------+
| Scheduler | --------> | Job 1 | <-------- | API Trigger |
| (자동 실행) | | (매일 23:00) | | (수동 실행) |
+---------------+ +--------------+ +---------------+
| |
+--------------------- 1:1 대응 ---------------------+
문제가 발생했거나 변경 사항이 생겼을 때, 또는 테스트나 점검을 위해 배치를 다시 실행해야 하는 상황에서 단순히 다음 정기 실행을 기다리는 것은 매우 비효율적이며, 실무 환경에서는 사실상 불가능에 가깝습니다. 어떻게 24시간을 기다리나요.
1
2
3
4
5
6
7
8
9
10
시간 흐름 →
6/3 6/4 6/5 6/6
──────────┬──────────┬──────────┬──────────┬──────────┐
23:00 23:00 23:00 23:00 ......
(실행 ❌) (이벤트) (이벤트) (이벤트)
↑
[현재 실행 할 이벤트]
- 6/3일 오류 발생으로, 재실행 필요
- 다음 23:00까지 기다려야 하는 것은 비효율적이며 실무에선 불가능
만약 언제든지 스케줄러/배치를 직접 실행하거나 재처리할 수 있다면 운영의 유연성과 안정성을 동시에 확보할 수 있겠죠. 단순히 API를 호출하는 방식만으로도, 운영자는 Job을 원하는 시점 에 직접 실행하거나 재처리할 수 있으니까요. 이를 통해 정기 실행 주기를 기다릴 필요 없이, 장애 발생 직후나 특정 데이터 변경 직후에 즉시 대응할 수 있어 실시간 처리가 가능합니다.
테스트나 점검이 필요한 상황에서도 실 운영 환경을 기준으로 안전하게 반복 실행해볼 수 있고, 일회성 처리가 필요한 경우에도 별도 코드 수정 없이 간단하게 대응할 수 있습니다.
2. 고려할 점
이러한 구조를 채택하게 되면 자연스럽게 발생하는 문제가 바로 중복 실행 과 롤백 입니다.
- 중복 실행
- 롤백
2-1. 중복 실행
API나 수동 명령어를 통해 배치를 여러 번 실행할 수 있도록 하면, 동일한 데이터에 대해 반복적으로 처리될 가능성이 생길 수 있습니다. 특히 실행 결과를 식별하거나 제어하는 기준이 명확하지 않으면, 동일한 로직이 여러 번 실행되어 데이터 중복, 상태 불일치가 발생하겠죠.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
자동 실행 수동 실행 수동 실행
│ │ │
▼ ▼ ▼
Job 실행 Job 실행 Job 실행
(23:00) (23:01) (23:02)
실패 실패 성공
│ │ │
▼ ▼ ▼
+-------------------- DB 테이블 ----------------------+
| id | report_date | status | created_at |
|----|-------------|--------|------------------------|
| 1 | 2025-06-03 | FAIL | 2025-06-03 23:00:02 | ← 자동 실행 실패
| 2 | 2025-06-03 | FAIL | 2025-06-03 23:01:00 | ← 수동 실행 실패
| 3 | 2025-06-03 | DONE | 2025-06-03 23:02:00 | ← 수동 실행 성공
+----------------------------------------------------+
물론 중복 실행 자체가 반드시 문제가 되는 것은 아닌데요, 실행 자체는 여러 번 발생할 수 있도록 허용하되, 그 과정에서 멱등성(idempotency) 만 보장되면 괜찮기 때문입니다. 즉, 동일한 배치가 여러 번 실행되더라도 그 결과는 한 번 실행된 것과 동일하면 되는 것이죠.
이를 위해서는 배치 로직 내부에서 명확한 실행 기준 을 정의하고, 이를 기반으로 이미 처리된 작업인지를 판단할 수 있어야 합니다. 또한 분산 환경에서 여러 인스턴스가 동시에 배치를 실행할 수 있는 구조라면, Redis 락이나 데이터베이스 락 등을 활용하여 실제 처리가 한 번만 이루어지도록 제어할 수도 있습니다.
이런 문제를 해결하기 위한 방법은 다양하기 때문에, 구체적인 해결 방법은 생략하도록 하겠습니다. 🎲
2-2. 롤백
수동 실행을 허용하는 구조에서는, 잘못된 파라미터 입력이나 조건 오류로 인해 의도하지 않은 데이터가 저장되거나 외부 시스템에 영향을 줄 가능성도 함께 존재합니다. 특히 운영자가 직접 실행하는 경우에는 테스트와 달리 실제 데이터에 반영되는 상황이기 때문에, 한번 잘못 실행된 작업이 장애로 이어질 수도 있습니다.
1
2
[자동 실행] ─▶ [배치 Job 실행 - (currency="USD", date="2025-06-03")]
[수동 실행] ─▶ [운영자가 직접 호출 (currency="KRW", date="2025-06-03")]
이러한 리스크를 최소화하려면, 실행 전에는 가능한 입력값 유효성 검증, 실행 후에는 이력 저장과 복구 수단을 마련해야 합니다. 예를 들어, DB에 처리 이력을 남기고 특정 시점 이전 상태로 되돌릴 수 있도록 하거나, 외부 시스템 호출 결과에 대해 되돌릴 수 있는 재처리 API를 준비하는 방식 등이 있습니다.
1
2
3
[자동 실행] ───┐
├─▶ [Job 실행 요청] ─▶ [이미 실행됨?] ───▶ 예 ─▶ Skip
[수동 실행] ───┘ └──▶ 아니오 ─▶ 실행 후 기록
실제로는 자동 실행보다 수동 실행에서 더 많은 실수가 발생할 수 있기 때문에, 실행 전후의 방어 로직과 사후 복구 설계가 중요해집니다. 특히 배치 작업이 파일을 생성하거나 외부 API를 호출하거나, 요금이 부과되는 작업일 경우엔 롤백이 단순하지 않기 때문에, 처리 전후 이력을 남기는 구조적 설계를 하도록 합니다.
2-3. 복잡도 증가
복잡도도 증가 합니다. 특히 자동 실행과 수동 실행이 병행되는 구조에서는 같은 Job이 중복 실행될 수 있기 때문에, 기본적인 배치 멱등성 외에도 수동 실행에 대한 더 엄격한 멱등성과 롤백 체계 보장이 요구됩니다. 이를 위한 로직이 추가되면 전체 구조가 복잡해지고 유지보수 부담도 커지겠죠.
1
2
3
4
5
6
7
8
9
10
[자동 실행] ───┐
├─▶ [Job 실행 요청] ─▶ [이미 실행됨?] ───▶ 예 ─▶ Skip
[수동 실행] ───┘ └──▶ 아니오 ─▶ [실행 후 기록]
│
▼
[결과 이상 감지됨?]
│
┌─────────────┬──────────┘
▼ ▼
[정상 처리 완료] [롤백 or 복구 수행]
자동 실행과 수동 실행은 같은 Job을 실행하더라도 진입 경로 가 다르기 때문에, 입력값 처리나 예외 상황 대응 방식에 차이가 발생할 가능성이 있습니다. 따라서 두 경로 모두에 대해 별도로 테스트해야 하며, QA 범위가 넓어지고 테스트 코드 유지와 검증 과정도 복잡해집니다. 따라서 이를 도입하기 전, 충분한 비용 검토가 필요합니다.
자동과 수동은 진입점이 달라 예외나 입력 처리에 차이가 생길 수 있어, 각각 별도로 테스트해야 합니다.
3. 정리
스케줄러/배치 작업의 실행은 권한이 있는 사람이라면 누구나, 언제든지, 필요할 때 호출할 수 있도록 개방되어 있어야 합니다. 그러나 그 내부 로직은 반드시 멱등성을 지켜야 하며, 동일한 요청에 대해서는 항상 한 번만 처리된 것과 같은 효과를 보장해야 합니다. 이러한 원칙을 기반으로 구조를 설계한다면, 스케줄링/배치 시스템은 운영의 유연성 과 안정성 은 물론, 개발과 테스트의 생산성 까지 모두 만족시킬 수 있을 것입니다. 🚀