fos-blog/study
01 / 홈02 / 카테고리
01 / 홈02 / 카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 커머스/F&B 도메인 설계 면접 대비 — 슬롯 경험을 주문·결제·쿠폰·매장 상태 설계로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • investing 페이지로 이동
    • stock-notes 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] CJ푸드빌 디지털 채널 Back-end 개발자 직무 분석
    • [초안] CJ푸드빌 디지털 채널 Back-end 면접 답변집 — 슬롯 도메인 경험을 커머스/F&B 설계로 번역하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/java/5장. 1절 : Job과 메타데이터 저장소
java

5장. 1절 : Job과 메타데이터 저장소

"배치가 도중에 죽어버리면 어떻게 할 것인가?" 현실의 배치 시스템은 완벽하지 않다. 네트워크 장애, 데이터베이스 연결 오류, 디스크 공간 부족 등 수많은 이유로 작업이 중단될 수 있다. 특히 대용량 데이터를 다룰 때는 이런 문제가 치명상이 된다. 이런 문제를 해결하려면 다음 기능들이 필요하다. 1. 어떤 작업이 실행되었는지 추적할 수 있어야 한다. 2....

2026.03.23·9 min read·60 views

"배치가 도중에 죽어버리면 어떻게 할 것인가?"

현실의 배치 시스템은 완벽하지 않다. 네트워크 장애, 데이터베이스 연결 오류, 디스크 공간 부족 등 수많은 이유로 작업이 중단될 수 있다. 특히 대용량 데이터를 다룰 때는 이런 문제가 치명상이 된다.

이런 문제를 해결하려면 다음 기능들이 필요하다.

  1. 어떤 작업이 실행되었는지 추적할 수 있어야 한다.
  2. 해당 작업이 어디까지 진행되었는지 상태를 저장할 수 있어야 한다.
  3. 실패한 작업을 처음부터가 아니라 실패 지점부터 재시작할 수 있어야 한다.

Spring Batch의 메타데이터 저장소

Spring Batch는 위 문제를 해결하기 위해 메타데이터 관리 시스템을 제공한다. 각 작업의 실행 상태, 처리한 항목 수, 실패 지점 등을 데이터베이스에 기록하고 추적한다. 이 덕분에 작업이 실패해도 마지막으로 성공한 지점부터 작업을 재개할 수 있다.

이번 장에서는 Spring Batch가 어떻게 이러한 실행 상태를 관리하는지, 메타데이터 저장소 테이블의 구조는 어떻게 구성되어있는지, 그리고 실패한 작업을 어떻게 효과적으로 재시작할 수 있는지 등을 자세히 알아보자.

Spring Batch의 핵심 도메인

JobInstance (Job의 논리적 실행 단위)

Job이 어떤 작업을 할지를 정의하는 설계도라면, JobInstance는 그 설계도를 기반으로 언제, 어떤 데이터로 실행되는지를 구체화한 실체다.

예를 들어보자.

text
Job: 월간 매출 정산 작업
    JobInstance#1 : 2024년 1월 매출 정산
    JobInstance#2 : 2024년 2월 매출 정산
    JobInstance#3 : 2024년 3월 매출 정산

그렇다면 Spring Batch는 어떻게 하나의 JobInstance를 다른 JobInstance와 구분할까? 무엇을 기준으로 이들을 서로 다른 실행으로 인식할까?

지금까지 작성했던, Job 설정 코드를 살펴보자. 모든 Job 정의에서 가장 먼저 한 일은 Job에 이름을 부여하는 것이었다.

java
new JobBuilder("monthlySalesSettlementJob", jobRepository)

이 Job의 이름은 애플리케이션 내의 수 많은 Job 중에서, 특정 Job을 식별하는 고유한 키 역할을 한다. 덕분에 커맨드라인에서 --spring.batch.job.name=monthlySalesSettlementJob 처럼 인자를 전달하면, Spring Batch는 어떤 Job을 실행해야 할지 알 수 있다.

그렇다면 동일한 Job 이름을 가진 JobInstance를 구분하는 기준은 무엇일까? 이 구분을 가능하게 하는 것이 바로 JobParameters다.

JobParameters (JobInstance를 식별하는 매개변수)

지금까지 우리는 JobParameters를 배치 작업을 동적으로 실행하기 위한 도구로만 사용해왔다. 하지만 Spring Batch 내부에서 JobParameters는 그보다 훨씬 중요한 역할을 수행한다.

JobParameters는 동일한 Job을 서로 다른 JobInstance로 구분하는 핵심 요소다. 간단히 말해, Job + JobParameters의 조합이 하나의 고유한 JobInstance를 결정한다.

JobExecution (JobInstance의 실제 실행 이력)

JobInstance가 Job의 논리적인 실행을 의미한다면, JobExecution은 그 JobInstance의 실제 실행 이력을 나타낸다.

이 구분이 왜 중요할까? 하나의 JobInstance(예: 2024년 4월 데이터 처리)가 여러 번 실행될 수 있기 때문이다.

배치 작업을 운영하다보면 다양한 이유로 실패가 발생한다. 네트워크 오류, 데이터베이스 문제, 디스크 공간 부족 등의 이유로 작업이 중단될 수 있다. 이런 상황에서 같은 JobInstance를 다시 실행해야 할 필요가 생긴다.

text
JobInstance(id=666, name="월간_매출_정산_작업", parameters="year=2024,month=4") {
 
    // 이 놈은 실패한 JobExecution
    JobExecution(id=101) {  // 첫 번째 실행 시도
        status: FAILED
        startTime: 2024-04-01 09:00:00
        endTime: 2024-04-01 09:05:23
        exitCode: FAILED
        failureExceptions: "org.springframework.batch.item.file.FlatFileParseException"
    }
 
 
    // 재실행. 이 놈은 성공했다
    JobExecution(id=102) {  // 두 번째 실행 시도 (재시작)
        status: COMPLETED
        startTime: 2024-04-01 14:30:00
        endTime: 2024-04-01 14:45:12
        exitCode: COMPLETED
        failureExceptions: null
    }
}

JobExecution에는 다음과 같은 주요 실행 정보가 포함된다.

  • 현재 상태: 작업이 현재 어떤 상태인지 (COMPLETED, FAILED, STOPPED 등)
  • 시작 시간: Job이 실행을 시작한 시점
  • 종료 시간: Job이 실행을 종료한 시점
  • 종료 코드: 해당 Job 실행의 최종 결과를 나타내는 코드
  • 실패 원인: 해당 Job 실행이 실패한 경우 해당 이유에 대한 정보

BatchStatus: 현재 상태

JobExecution은 COMPLETED, FAILED, STOPPED 등의 실행 상태를 가질 수 있고, 이 상태를 BatchStatus라고 한다. Job 또는 Step의 실행 상태를 나타내는 열거형(Enum)이다.

이 상태 정보는 메타데이터 저장소에 저장되며, 배치 작업의 실행 흐름을 추적하고 관리하는 데 핵심적인 역할을 한다.

JobInstance, JobExecution, BatchStatus까지 알아봤으니, 이제 Spring Batch의 중요한 실행 원칙을 소개한다.


JobInstance의 재실행 제한

Spring BAtch는 한 번 완료된 JobInstance는 재실행할 수 없도록 제한한다. 다시 말해 한 번 성공적으로 완료된 Job을 동일한 JobParameters로 다시 실행하려 하면 Spring Batch는 예외를 발생시킨다. 이는 동일한 작업이 중복 실행되어 발생할 수 있는 부작용을 방지하기 위한 안전장치다.

이 원칙에 앞서 살펴본 도메인 개념들이 어떻게 상효작용하는지 알아보자.

Spring Batch는 Job을 실행할 때 마다 동일한 JobInstance가 과거에 성공적으로 완료된 적이 있는지 검사한다.

다시 말해 해당 JobInstance의 실행 이력(JobExecution)중 BatchStatus가 COMPLETED인 실행 이력이 존재하는지를 검사한다.

만약 존재한다면 JobInstanceAlreadyCompleteException을 발생시키며 작업의 재실행을 거부한다. 반면 COMPLETED 상태의 JobExecution이 없는 경우에는 해당 JobInstance를 다시 실행할 수 있다.

그런데 메타데이터 테이블을 날려버리지 않고도 동일한 Job을 반복해서 실행하고 싶을 수 있지 않은가? 멱등성이 보장되어 여러 번 실행해도 상관없는 Job이라면?

방법이 있다. 이런 상황을 위해 Spring Batch에서는 다음과 같은 기능을 제공한다. 바로 JobParametersIncrementer다.

JobParametersIncrementer로 동일 작업 여러 번 실행하기

JobParametersIncrementer는 JobParameters에 파라미터를 추가하거나, 수정하여, 동일한 Job을 여러 번 실행할 수 있게 해주는 특수 컴포넌트다.

메서드 시그니처는 다음과 같다.

java
public interface JobParametersIncrementer {
    JobParameters getNext(@Nullable JobParameters paramters);
}

입력으로 JobParameters를 받아서, 새로운 JobParameters를 반환하다. 이 새로운 JobParameters가 생성됨에 따라 Spring Batch는 매번 다른 JobInstance로 인식하게 된다.

RunIdIncrementer: 시스템 우회 기술

RunIdIncrementer는 JobParametersIncrementer의 대표적인 구현체다. 이 컴포넌트는 각 배치 실행마다 run.id라는 이름의 파라미터 값을 자동으로 증가시킨다. 예를 들어,

  • 첫 실행 : run.id=1
  • 두 번째 실행 : run.id=2
  • 세 번째 실행 : run.id=3

RunIdIncrementer 적용하기

java
@Bean
public Job firstJob() {
    return new JobBuilder("firstJob", jobRepository)
        .incrementer(new RunIdIncrementer())
        .start(...)
        .build();
}

이렇게만 추가하면 동일한 파라미터로 여러 번 실행해도 예외가 발생하지 않고 매번 새로운 JobInstance로 인식된다.

JobParameters의 로그를 보면, 각 파라미터에 identifying=true라는 속성이 설정되어있는 것이 보일 것이다. 이 속성의 의미와 역할을 알아보자.


잡 파라미터의 신원 확인: identifying

여기까지 왔다면 JobParameters가 단순한 데이터 전달체가 아니라는 사실을 꺠달았을 것이다. JobInstance를 식별하는 핵심 키로 작동한다. 하지만 모든 파라미터가 식별 용도로 사용되는 것은 아니며, 여기서 등장하는 것이 identifying 속성이다.

이 솏성은 해당 잡 파라미터가 JobInstance를 식별하는 데 사용되는지 여부를 결정한다. 기본적으로 모든 JobParameter는 identifying=true로 설정된다.

때로는 파라미터가 실행마다 달라져도 같은 JobInstance로 취급해야 할 경우가 있다. 예를 들어,

  • 로깅 수준 제어: verbose=true로 상세 로깅을 켜고 끄는 파라미터
  • 성능 튜닝 변수: chunk.size=1000과 같은 실행 성능에만 영향을 주는 파라미터

이런 파라미터들은 작업의 논리적 정체성을 바꾸지 않는다. 같은 데이터를 처리하지만 처리 방식만 다를 뿐이다.

1장에서 소개했던 [JobParameter의 표기법](1.2-job-parameters.md)을 기억하는가?

sh
parameterName=paramterValue,parameterType,identifying

커맨드 라인에서 identifying 속성을 false로 설정하려면 다음과 같이 하면 된다. 예시를 보자.

sh
./gradlew bootRun --args='--spring.batch.job.name=first verbose=true,java.lang.String,false'

이렇게 하면 verbose 파라미터는 JobInstance를 식별하는 데 사용되지 않는다.


Job의 재실행 통제: restarable 설정

Spring Batch의 Job은 기본적으로 실패한 경우 재시작이 가능하다. 하지만 모든 Job이 재시작 가능해야 하는 건 아니다. 비즈니스적인 이유로 작업이 한 번 실패하면 다시는 실행되지 않도록 해야 할 수도 있다.

Job의 재시작 불가능 설정 방법

이런 상황을 위해 Spring Batch는 preventRestart() 메서드를 제공한다. 이 메서드는 JobBuilder의 메서드로, 설정된 Job이 재시작 불가능하도록 만든다.

java
@Bean
public Job firstJob() {
    return new JobBuilder("firstJob", jobRepository)
        .start(...)
        .preventRestart()
        .build();
}

이 설정이 적용된 Job이 실패한 후 다시 실행하려고 하면, JobRestartException을 발생시키며 작업의 재실행을 차단한다.

preventRestart() 설정은 강력한 제약이다. 이 설정이 적용된 Job은 어떤 이유로든 실패하면 같은 파라미터로는 다시 실행할 수 없다. 그렇기에 이 작업이 실패하면 재시작하면 안되는 명확한 이유가 있는가?라는 질문을 던져보자.

특히 restartable 설정과 앞서 배운 RunIdIncrementer 설정을 함께 사용할 떄는 주의하자. RunIdIncrementer는 매번 다른 run.id 값을 생성하여 새로운 JobInstance를 만들기 떄문에, 실제로는 preventRestart() 설정의 효과가 무력화된다.

이제 또 다른 핵심 도메인 개념을 살펴보자.


StepExecution (Step의 실행 이력)

StepExecution은 단일 Step의 실행 이력을 나타내는 객체다. JobExecution이 Job의 실행 이력을 나타내듯, StepExecution은 Step의 실행 이력을 나타낸다.

하나의 Job이 여러 Step으로 구성될 수 있기 때문에, 하나의 JobExecution은 여러 개의 StepExecution을 포함할 수 있다.

text
JobExecution {
    StepExecution("step1")
    StepExecution("step2")
    StepExecution("step3")
}

StepExecution은 Step 실행 시 생성되며, 해당 Step이 실제로 시작될 때만 생성된다. 예를 들어, 첫 번째 Step이 실패하면 두 번째 Step은 실행되지 않으므로, 두 번쨰 Step에 대한 StepExecution도 생성되지 않는다.

StepExecution은 다음과 같은 주요 실행 정보를 포함한다. 이 정보들은 배치 작업의 모니터링과 문제 해결에 매우 중요하다. 예를 들어, 읽기 카운트와 쓰기 카운트의 차이를 통해 처리 중 얼마나 많은 아이템이 필터링 되었는지 확인 할 수 있다. 또한 롤백 카운트와 스킵 카운트는 작업 중 바생한 오류의 수를 파악하는 데 도움이 된다.

  • 현재 상태: Step이 현재 어떤 상태인지를 나타내는 BatchStatus
  • 읽기/쓰기 카운트: 성공적으로 읽거나 쓴 아이템의 수
  • 커밋/롤백 카운트: 트랜잭션 처리 횟수
  • 스킵 카운트: 청크 처리 중 건너뛴 아이템의 수
  • 시작/종료 시간: Step 실행의 시간적 정보
  • 종료 코드: Step 실행의 최종 결과 코드
  • 예외 정보: 실패 시 발생한 오류에 대한 상세 정보

위 항목중에 BatchStatus는 JobExecution과 마찬가지로 StepExecution에도 현재 실행 상태를 의미하는데 사용된다. JobExecution의 BatchStatus와 StepExecution의 BatchStatus는 면밀한 관계가 존재한다.


JobExecution과 StepExecution의 BatchStatus

JobExecution의 최종 BatchStatus는 해당 JobExecution에서 가장 마지막에 실행된 StepExecution의 BatchStatus 값을 기준으로 결정된다. 이 원리를 다양한 시나리오로 살펴보자.

시나리오 1: 모든 Step이 성공한 경우

text
JobExecution {
    StepExecution#1 ("step1", BatchStatus.COMPLETED)
    StepExecution#2 ("step2", BatchStatus.COMPLETED)
    // JobExecution의 최종 status: BatchStatus.COMPLETED
}

마지막에 실행된 BatchStatus가 COMPLETED 이므로, JobExecution의 최종 BatchStatus도 COMPLETED가 된다.

시나리오 2: 중간에 Step이 실패한 경우

text
JobExecution {
    StepExecution#1 ("step1", FAILED) // 실패!
    StepExecution#2 ("step2") // 실행되지 않음
    // JobExecution의 최종 status: BatchStatus.FAILED
}

이 경우, StepExecution#1이 실패했기 떄문에 StepExecution#2는 아예 실행되지 않는다. 따라서, 가장 마지막으로 실행된 StepExecution#1의 상태가 FAILED 이므로 JobExecution의 최종 상태 역시 FAILED로 결정된다.

이러한 상태 전파 원리를 이해했으니, 배치 작업이 실패하고 재시작될 때 어떻게 동작하는지 알아보자.

실패와 재시작 시 StepExecution의 동작

잡이 실패 후 다시 실행되면 JobExecution이 새로 생성되는 것과 마찬가지로, 스텝을 다시 실행할 때도 새로운 StepExecution이 생성된다. 이는 StepExecution이 JobExecution에 종속되어 있기 때문이다.

새로운 JobExecution이 생성되면 그에 속한 모든 StepExecution도 새롭게 생성되는 것이다.

아래 예시를 보자. 첫 번째 실행에서 step2가 실패한 후, JobExecution#2로 재시작했을 때 동일한 step2에 대해 StepExecution#3이 새롭게 생성되는 것을 볼 수 있다.

text
JobExecution#1 (FAILED) {
    StepExecution#1 ("step1", COMPLETED)
    StepExecution#2 ("step2", FAILED)    // 이놈이 실패했다
}
 
JobExecution#2 (COMPLETED) {
    // StepExecution#1 ("step1")은 이미 성공했으므로 다시 생성되지 않음
    StepExecution#3 ("step2", COMPLETED) // 실패한 step2부터 재시작
}

이미 성공적으로 완료된 Step은 재실행되지 않으므로 새로운 StepExecution도 생성되지 않는다

StepExecution의 정보는 단순한 로깅 이상의 가치가 있다. 다음과 같이 활용할 수도 있을 것이다.

  1. 성능 모니터링: 각 Step의 처리 시간과 처리량을 분석하여 병목 지점 식별
  2. 오류 패턴 분석: 특정 데이터나 조건에서 반복적으로 오류가 발생하는지 파악
  3. 자원 할당 최적화: 읽기/처리/쓰기 비율을 분석하여 리소스 할당 조절

이제 마지막으로 살펴볼 컴포넌트는 바로 ExecutionContext다. 이 컴포넌트는 배치 작업의 상태를 저장하고 복원하는 데 있어 핵심적인 역할을 수행한다.


ExecutionContext (사용자 정의 데이터 보관함)

간단히 발해 배치 작업의 상태 정보를 저장하는 데이터 컨테이너다. Key-Value 형태로 데이터를 저장하고 관리한다.

특히 비즈니스 로직 처리 중에 발생하는 사용자 정의 데이터를 관리할 방법이 필요한데, 이떄 사용하는 것이 바로 ExecutionContext다.

java
// ExecutionContext 예시
ExecutionContext {
    "processingIndex": 42500,              // 마지막으로 처리한 항목 인덱스
    "totalAmount": 2750000.00,             // 중간 집계 결과
    "lastProcessedId": "TRX-20240315-789", // 마지막으로 처리한 거래 ID
}

4장에서 살펴본 ItemStream 인터페이스의 open()과 update() 메소드를 떠올려 보자.

ItemStream.open() 메서드는 ExecutionContext에서 이전 실행 상태 정보를 복원하고, update() 메서드는 현재 실행 중인 상태 정보를 ExecutionContext에 저장했다.

이 처럼 이전 상태를 가져오고, 현재 상태를 저장할 수 있었던 이유가 바로 ExecutionContext가 메타데이터 저장소에 영구적으로 저장되기 때문이다.

이것이 Spring Batch의 강력한 재시작 기능의 핵심이다.

ExecutionContext의 범위: Job vs Step

1장에서 설명했듯이, JobExecution 수준의 ExecutionContext와 StepExecution 수준의 ExecutionContext가 별도로 존재한다. 실제 메타데이터 저장소에서도 물리적으로 분리되어 보관되며, 서로 다른 테이블 구조로 저장되고 관리된다.

on this page
  • Spring Batch의 메타데이터 저장소
  • 01Spring Batch의 핵심 도메인
  • JobInstance (Job의 논리적 실행 단위)
  • JobParameters (JobInstance를 식별하는 매개변수)
  • JobExecution (JobInstance의 실제 실행 이력)
  • 02JobInstance의 재실행 제한
  • JobParametersIncrementer로 동일 작업 여러 번 실행하기
  • RunIdIncrementer: 시스템 우회 기술
  • 03잡 파라미터의 신원 확인: identifying
  • 04Job의 재실행 통제: restarable 설정
  • Job의 재시작 불가능 설정 방법
  • 05StepExecution (Step의 실행 이력)
  • 06JobExecution과 StepExecution의 BatchStatus
  • 시나리오 1: 모든 Step이 성공한 경우
  • 시나리오 2: 중간에 Step이 실패한 경우
  • 실패와 재시작 시 StepExecution의 동작
  • 07ExecutionContext (사용자 정의 데이터 보관함)
  • ExecutionContext의 범위: Job vs Step

댓글 (0)