[Spring Batch] Job의 재시작을 막는 preventRestart

이종찬·2025년 7월 21일
post-thumbnail

Spring Batch의 강력한 기능 중 하나는 실패한 Job을 이어서 실행할 수 있는 '재시작(Restart)' 기능입니다.

하지만 경우에 따라 재시작을 허용하면 데이터가 중복되거나 시스템이 꼬여버리는 심각한 문제가 발생할 수 있습니다.

이번 글에서는 Job의 재시작을 의도적으로 막는 preventRestart() 옵션의 역할과, 멱등성이라는 개념을 중심으로 언제 이 옵션을 사용해야 하는지 알아보겠습니다.

스프링 배치의 재시작(Restart)

preventRestart를 이해하려면, 먼저 Spring Batch가 기본적으로 재시작을 허용한다는 점을 알아야 하며, Spring Batch의 아주 중요한 특징 중 하나입니다.

동작 방식 (예시)

다음과 같은 3단계로 구성된 Job이 있다고 가정해 보겠습니다.

  • Step 1: 사용자 정보 읽기
  • Step 2: 이메일 주소 검증하기
  • Step 3: 환영 메일 발송하기

이 Job이 실행되다가 Step 3에서 메일 서버 문제로 실패했다면 어떻게 될까요?

  • Step 1: 사용자 정보 읽기 (성공)
  • Step 2: 이메일 주소 검증하기 (성공)
  • Step 3: 환영 메일 발송하기 (실패)

개발자가 메일 서버 문제를 해결한 뒤, 동일한 파라미터로 Job을 다시 실행하면 Spring Batch는 데이터베이스에 기록된 이전 실행 상태(JobExecution)를 확인합니다. 그리고 다음과 같이 동작합니다.

  1. Step 1과 Step 2는 이미 COMPLETED 상태인 것을 보고 건너뜁니다.
  2. 이전에 FAILED 상태였던 Step 3부터 실행을 다시 시작합니다.

preventRestart() 옵션의 역할과 사용법

preventRestart() 옵션의 역할은 이름 그대로 아주 직관적입니다. 바로 Job의 재시작을 막는 기능입니다.
이 옵션을 설정하면, 해당 Job은 재시작이 불가능한 '일회성' 작업이 됩니다.

만약 preventRestart()가 설정된 Job이 실행되다가 FAILED 상태가 되면, 해당 JobInstance(Job 이름 + 파라미터)의 생명주기는 거기서 종료됩니다.

동일한 파라미터로 다시 실행하려고 시도하면 Spring Batch는 즉시 JobRestartException 예외를 발생시키며 실행을 거부합니다. 단, 파라미터를 변경하여 실행하는 것은 새로운 JobInstance로 간주되므로 정상적으로 실행됩니다.

사용법은 매우 간단합니다. Job 설정에 단 한 줄만 추가하면 됩니다.

    @Bean
    public Job myJob() {
        return new JobBuilder("myJob", jobRepository)
                .start(step1())
                .preventRestart() // <-- 이렇게 한 줄만 추가하면 됩니다!
                .build();
    }

preventRestart()는 언제 사용할까?

결론부터 말하면, preventRestart()는 Job의 동작이 멱등성(Idempotent)을 보장하지 않을 때 사용합니다. 멱등성이란, 연산을 여러 번 수행해도 그 결과가 한 번 수행한 것과 동일한 성질을 의미합니다.

재시작을 했을 때 데이터가 중복되거나 꼬일 위험이 있는 Job에 이 옵션을 사용해 재시작을 차단합니다.

주요 활용 시나리오

  1. 데이터를 덮어쓰지 않고 추가(Append)하는 Job

    • 상황: 매일의 거래 로그를 하나의 파일 끝에 계속 이어서 쓰는 Job을 생각해 보세요.
    • 문제점: 만약 Job이 로그를 일부 기록한 뒤 실패하고 재시작된다면, 똑같은 로그가 파일에 두 번 기록되어 데이터가 오염됩니다.
    • 해결책: .preventRestart()를 설정해 실패 시 Job을 중단시키고, 개발자가 직접 원인을 파악하고 수동으로 조치하도록 합니다.
  2. 한 번만 실행되어야 하는 외부 API 호출 Job

    • 상황: 사용자에게 '한 번만 사용 가능한' 프로모션 쿠폰을 발송하는 Job.
    • 문제점: 500명에게 쿠폰을 보낸 뒤 Job이 실패하고 재시작된다면, 앞서 쿠폰을 받은 500명이 중복으로 쿠폰을 또 받게 될 수 있습니다.
    • 해결책: .preventRestart()로 재시작을 막아 중복 발송의 위험을 없애고, 실패 시 어디까지 작업이 성공했는지 수동으로 확인하고 조치합니다.
  3. Job 실행 시마다 고유한 리소스를 생성하는 Job

    • 상황: Job이 시작될 때마다 고유한 이름의 임시 디렉터리를 생성해야 하는 경우.
    • 문제점: Job이 실패하면 임시 디렉터리는 그대로 남아있습니다. 재시작 시 똑같은 이름의 디렉터리를 또 만들려고 하다가 '이미 디렉터리가 존재함' 오류로 인해 또 실패하게 됩니다.
    • 해결책: .preventRestart()를 걸어두면, 실패 후 재시작 시도를 막고 개발자가 직접 남아있는 임시 디렉터리를 정리하고 새 파라미터로 다시 실행하도록 유도할 수 있습니다.

결론

결론적으로 preventRestart()는 단순한 기능이 아니라, 개발자가 "해당 Job은 멱등성을 보장하는가?"를 스스로 묻게 만드는 중요한 장치입니다.

재시작 시 데이터가 꼬일 위험이 조금이라도 있다면, preventRestart()를 설정하여 의도치 않은 동작을 방지 하는 것이 좋습니다.

profile
왜? 라는 질문이 사라질 때까지

0개의 댓글