Spring Batch 입문 8편 — JobOperator (실행 · 중지 · 재시작 · CommandLine)

2026-05-17Spring Batch 입문에서 운영까지

Spring Batch 입문 8편. JobOperator 깊이 — start·stop·restart·recover·abandon 같은 라이프사이클 제어, 메타데이터 조회 API, v6 신규 CommandLineJobOperator (CLI 표준), Spring Boot 자동 등록, 운영 환경 사용 패턴까지 풀어쓴 학습 노트.

📚 Spring Batch 입문에서 운영까지 · 8편 — JobOperator (실행 · 중지 · 재시작 · CommandLine)

이 글은 Spring Batch 입문에서 운영까지 시리즈 48편 중 8편이에요. 7편 에서 JobRepository(메타데이터 영속 저장소) 의 영속화를 잡았다면, 이번 8편은 그 위에 얹히는 제어 인터페이스인 JobOperator 차례예요.

JobOperator의 자리

[CLI / REST / Spring Scheduler / Kubernetes Job]
                  ↓
            [JobOperator] ←── 통합 제어 인터페이스
                  ↓
            [JobLauncher] ← Job 실제 실행
                  ↓
            [JobRepository] ← 상태 영속
                  ↓
            [Job → Step → Reader/Processor/Writer]

외부 시스템이 Job 과 통신할 때면 결국 JobLauncher(Job 실행 진입점) 보다 한 단계 위에 놓인 이 인터페이스를 거치게 돼요.

핵심 메서드 6가지

public interface JobOperator {
    Long start(Job job, JobParameters parameters);              // 새 실행
    Long startNextInstance(Job job);                             // 자동 incremented parameters
    boolean stop(long executionId);                              // 중지 요청
    Long restart(long executionId);                              // 재시작
    Long recover(long executionId);                              // v6 신규: 수동 복구
    Long abandon(long executionId);                              // 포기 (재시작 불가)
    // 외 메타데이터 조회
}

여섯 메서드를 하나씩 짚어 볼게요.

1. start(Job, JobParameters)

새 JobInstance(같은 parameters 묶음의 실행 단위) 를 시작해요.

@Autowired
private JobOperator jobOperator;

@Autowired
private Job myJob;

public void launch() {
    JobParameters params = new JobParametersBuilder()
        .addString("targetDate", "2026-05-17")
        .addLong("run.id", System.currentTimeMillis())
        .toJobParameters();

    Long executionId = jobOperator.start(myJob, params);
    log.info("Started execution {}", executionId);
}

같은 JobParameters(Job 실행에 넘기는 키-값 묶음) 로 두 번 호출하면 두 번째에서 JobInstanceAlreadyCompleteException(같은 instance 가 이미 완료) 이 떨어져요.

2. startNextInstance(Job)

Incrementer(파라미터 자동 증가 컴포넌트) 를 활용해 매번 자동으로 증가된 parameters 로 실행해요.

Long executionId = jobOperator.startNextInstance(myJob);

Job 정의에 RunIdIncrementer(run.id 를 1씩 올리는 기본 구현) 또는 custom incrementer 가 박혀 있어야 동작해요.

3. stop(long executionId)

boolean stopped = jobOperator.stop(executionId);

이름은 stop 이지만 즉시 중지가 아니에요. 지금 돌고 있는 chunk·step 의 자연스러운 종료점까지 끌고 가서 멈추기 때문에 실제로는 수 분 뒤에 멈춰요.

상태도 STOPPING 을 거쳐 STOPPED 로 넘어가요.

4. restart(long executionId)

Long newExecutionId = jobOperator.restart(failedExecutionId);

STOPPED·FAILED 인 같은 JobInstance 위에 새 JobExecution(한 번의 실행 시도 단위) 을 만들고, ExecutionContext(실행 중 상태 저장 맵) 부터 이어 재개해요.

조건:

  • 기존 execution = STOPPED 또는 FAILED
  • Job 의 preventRestart()(재시작 차단 설정) 안 박혀 있음

5. recover(long executionId) — v6 신규

Long newExecutionId = jobOperator.recover(failedExecutionId);

FAILED execution 을 RECOVERED 상태로 마킹하고 새 JobExecution 을 만들어요.

언제 쓰냐면, 외부 시스템 cleanup·중복 데이터 제거 같은 수동 정리를 끝낸 뒤 "복구됐다"는 사실을 명시할 때예요.

6. abandon(long executionId)

jobOperator.abandon(executionId);

STOPPED 또는 FAILED execution 을 ABANDONED 로 옮겨 재시작을 영구히 막아요.

데이터 자체가 무효라 재시작 의미가 없을 때 쓰는 카드예요.

메타데이터 조회 API

// Job 이름 목록
Set<String> jobNames = jobOperator.getJobNames();

// 특정 Job 의 JobInstance 들
List<Long> instanceIds = jobOperator.getJobInstances("myJob", 0, 10);

// 특정 instance 의 execution 들
List<Long> executionIds = jobOperator.getExecutions(instanceId);

// 현재 실행 중인 executions
Set<Long> running = jobOperator.getRunningExecutions("myJob");

// 특정 execution 요약
String summary = jobOperator.getSummary(executionId);

// execution context 조회
Map<Long, String> contexts = jobOperator.getStepExecutionSummaries(executionId);

// JobParameters
Properties params = jobOperator.getParameters(executionId);

7편의 JobExplorer(메타데이터 조회 전용 API) 와 영역이 겹치는데, JobOperator 는 거기에 제어까지 묶어 가진 게 차이예요.

Spring Boot 자동 등록

@SpringBootApplication
public class MyApp {
    @Autowired
    private JobOperator jobOperator;        // 자동 주입
}

Spring Boot 가 기본 JobOperator bean 을 알아서 만들어 주기 때문에 별도로 @Bean 을 정의할 필요가 없어요.

CommandLineJobOperator (v6 신규)

CLI 표준 도구로, 4편의 What's New 에서 잠깐 본 자리예요.

기본 사용

# Job 시작
$ java -jar my-batch.jar --job=myJob targetDate=2026-05-17

# Job 중지
$ java -jar my-batch.jar --stop --execution-id=42

# 재시작
$ java -jar my-batch.jar --restart --execution-id=42

# Recover
$ java -jar my-batch.jar --recover --execution-id=42

# Abandon
$ java -jar my-batch.jar --abandon --execution-id=42

# 메타데이터
$ java -jar my-batch.jar --list-jobs
$ java -jar my-batch.jar --list-instances=myJob
$ java -jar my-batch.jar --list-executions=instanceId
$ java -jar my-batch.jar --summary --execution-id=42

활성

@SpringBootApplication
public class BatchCli {
    public static void main(String[] args) {
        System.exit(SpringApplication.run(BatchCli.class, args).getBean(CommandLineJobOperator.class).run(args));
    }
}

이렇게 한 클래스에서 띄우거나, 따로 main class 를 두는 식으로 활성해요.

운영 환경 패턴

# Cron + Kubernetes CronJob
# crontab
0 2 * * * /usr/local/bin/java -jar /opt/batch/my-batch.jar --job=dailyReport targetDate=$(date +%Y-%m-%d)

또는 K8s CronJob(쿠버네티스 cron 스케줄 리소스):

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: batch
              image: my-batch:1.0.0
              args:
                - --job=dailyReport
                - "targetDate=$(date +%Y-%m-%d)"

REST API 통합

@RestController
@RequestMapping("/api/jobs")
public class JobController {

    @Autowired
    private JobOperator jobOperator;

    @Autowired
    private JobRegistry jobRegistry;

    @PostMapping("/{name}/start")
    public ResponseEntity<?> start(@PathVariable String name,
                                    @RequestBody Map<String, String> params) throws Exception {
        Job job = jobRegistry.getJob(name);
        JobParameters parameters = buildParameters(params);
        Long executionId = jobOperator.start(job, parameters);
        return ResponseEntity.ok(Map.of("executionId", executionId));
    }

    @PostMapping("/{executionId}/stop")
    public ResponseEntity<?> stop(@PathVariable long executionId) throws Exception {
        boolean stopped = jobOperator.stop(executionId);
        return ResponseEntity.ok(Map.of("stopped", stopped));
    }

    @PostMapping("/{executionId}/restart")
    public ResponseEntity<?> restart(@PathVariable long executionId) throws Exception {
        Long newId = jobOperator.restart(executionId);
        return ResponseEntity.ok(Map.of("newExecutionId", newId));
    }

    @GetMapping("/{executionId}")
    public ResponseEntity<?> status(@PathVariable long executionId) {
        String summary = jobOperator.getSummary(executionId);
        return ResponseEntity.ok(summary);
    }
}

여기에 JobRegistry(이름으로 Job 을 찾는 레지스트리) 를 같이 끼우면 운영 dashboard·monitoring API 의 기반이 그대로 잡혀요.

Spring Cloud Data Flow 통합

대규모 운영은 Spring Cloud Data Flow (SCDF) 로 풀어요. Job 등록·실행·모니터링·재시작을 UI·CLI·API 한 묶음으로 묶은, JobOperator 위에 한 겹 더 얹은 추상화예요.

SCDF(Spring Cloud Data Flow 의 약자) 는 복잡한 미세 제어가 없어도 되는 환경에서 강한데, 회사 환경에 따라 SCDF 를 그대로 쓰거나 REST + UI 를 직접 짜는 쪽으로 갈려요.

Exception 처리

try {
    Long executionId = jobOperator.start(job, parameters);
} catch (JobInstanceAlreadyCompleteException e) {
    // 같은 parameters 의 JobInstance 가 이미 COMPLETED
    log.error("Already completed", e);
} catch (JobExecutionAlreadyRunningException e) {
    // 같은 JobInstance 가 실행 중
    log.error("Already running", e);
} catch (JobRestartException e) {
    // 재시작 불가 (preventRestart 또는 ABANDONED)
    log.error("Cannot restart", e);
} catch (JobParametersInvalidException e) {
    // Validator 실패
    log.error("Invalid parameters", e);
}

각 exception 의 의미를 명확히 잡아 두면 운영 대응이 그만큼 빨라져요.

운영 시나리오

시나리오 1: Long-running Job 정리

# 현재 running execution 확인
$ java -jar batch.jar --running --job=hugeJob
[ executionId=42, started=2026-05-17 02:00, status=STARTED ]

# 안전 중지
$ java -jar batch.jar --stop --execution-id=42

# 수 분 후 확인
$ java -jar batch.jar --summary --execution-id=42
status=STOPPED

# 재시작
$ java -jar batch.jar --restart --execution-id=42

시나리오 2: Failed Job 의 수동 정리

# 실패 확인
$ java -jar batch.jar --summary --execution-id=42
status=FAILED, exception=...

# 외부 시스템 cleanup (다른 process 가 진행)
# ... cleanup 완료

# Recover 마킹 + 새 execution
$ java -jar batch.jar --recover --execution-id=42

시나리오 3: 영구 abandon

# 데이터 자체가 잘못된 경우
$ java -jar batch.jar --abandon --execution-id=42

# 다음 새 instance 시작 (새 parameters)
$ java -jar batch.jar --job=myJob targetDate=2026-05-18

한계·실무 함정

1. Stop 의 지연

stop() 은 즉시가 아니라 chunk·step 의 자연스러운 종료점까지 기다려요. 운영 자동화 코드에서 바로 다음 명령을 이어 던지면 아직 STARTED 인 상태일 수 있어서 polling 이 필요해요.

2. Restart 의 조건

preventRestart() 가 박힌 Job 이나 ABANDONED execution 은 재시작이 안 돼요. exception 처리를 꼭 같이 박아 둬야 해요.

3. ExecutionId 추적

JobOperator API 는 전부 Long executionId 를 키로 받아요. 운영 도구 쪽에서 executionId 를 CI/CD·monitoring DB 어딘가에 보관해 두는 작업이 따라붙어요.

4. JobParameters 의 type

addString·addLong·addDouble·addDate 처럼 타입을 명시해 넣게 돼 있어요. CLI 의 param=value 는 기본이 String 이라 타입 변환을 직접 챙겨 줘야 해요.

5. 동시 운영자 충돌

JobOperator 호출은 DB transaction 안에서 도는데, 여러 operator 가 같은 instance 를 동시에 만지면 race 가 나요. SERIALIZABLE isolation 으로 DB 단에서 차단하는 쪽이 안전해요.

6. CommandLineJobOperator 의 logging

CLI 로 실행하면 Spring Boot startup 로그가 폭증해요. production-ready logging 설정을 같이 잡아 주세요.

logging:
  level:
    root: WARN
    org.springframework.batch: INFO

시험 직전 한 번 더 — JobOperator 함정 압축 노트

  • JobOperator = Job 실행·제어·메타데이터 통합 인터페이스
  • 6가지 메서드 = start·startNextInstance·stop·restart·recover·abandon
  • start(Job, JobParameters) = 새 실행, 같은 parameters 두 번 = JobInstanceAlreadyCompleteException
  • startNextInstance(Job) = Incrementer 활용 자동 parameter
  • stop(executionId) = 즉시 X, chunk 자연 종료점에서 (STOPPING → STOPPED)
  • restart(executionId) = STOPPED·FAILED 의 새 JobExecution, ExecutionContext 부터 재개
  • recover(executionId) (v6 신규) = FAILED → RECOVERED 마킹 + 새 execution
  • abandon(executionId) = 영구 차단 (ABANDONED)
  • 메타데이터 조회 = getJobNames·getJobInstances·getExecutions·getRunningExecutions·getSummary·getParameters
  • Spring Boot 자동 등록 = @Autowired JobOperator 만으로
  • CommandLineJobOperator (v6 신규) = CLI 표준
  • CLI 명령 — --job·--stop·--restart·--recover·--abandon·--list-*·--summary
  • 운영 패턴 — Cron / Kubernetes CronJob + CommandLineJobOperator
  • REST API 통합 = @RestController + JobOperator 주입
  • Spring Cloud Data Flow = 대규모 운영 UI·CLI·API 통합 (옵션)
  • Exception — JobInstanceAlreadyCompleteException·JobExecutionAlreadyRunningException·JobRestartException·JobParametersInvalidException
  • 시나리오 — Long-running 정리·Failed 수동 cleanup + Recover·영구 abandon
  • 함정 — Stop 지연 (polling 필요)
  • 함정 — preventRestart·ABANDONED = restart X
  • 함정 — JobParameters 의 type 명시
  • 함정 — 동시 operator 충돌 (SERIALIZABLE isolation 보장)
  • 함정 — CLI logging 폭증

공식 문서: Configuring a JobOperator 에서 원문을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!