지금까지 알고 있던, 업무상으로 해오던 스프링 배치에 대해 정리했다.
배치란 대량 일괄 작업을 뜻하며 몇 시간에 한번씩 실행되는 등의 스케줄 개념은 포함되지는 않는다.
이점
스프링 웹 MVC와는 달리 스프링 배치를 사용하면 편리한 이유는 아래와 같다.
- 특정 작업 단위만큼 트랜잭션 처리를 하여 메모리에 한번에 올리는 양이 적기 때문에 OOM으로부터 상대적으로 안전하고 작업에 문제가 있으면 근방에서 다시 시작할 수 있다. (Chunk Oriented)
- 성공한 작업 건에 대해서 실수로 재실행을 하더라도 작업이 수행되지 않는다. (ItemStream)
- 작업을 세부적으로 나누어, 성공/실패시에 어떤 작업을 할 지 쉽게 지정할 수 있다. (Flow)
- 무엇이 잘못되었는지 기본적으로 제공하는 이력 테이블을 통해 쉽게 알 수 있다. (Meta table)
활용처
그리고 아래와 같은 상황에서 활용할 수 있다.
- 일 매출 집계
- 통계 데이터 및 데이터 가공
- 대량의 데이터 크롤링 및 가공
- 일간, 주간, 월간 정산
작업 단위
작업의 크기 순으로 정렬하면 아래와 같다.
1. Job
2. Step
3. Tasklet
이때 Tasklet에 대응되는 것이 ItemReader, ItemProcessor, ItemWriter이고 (이후 Item prefix는 제외할 수 있으면 제외하여 표기) ItemProcessor는 생략할 수 있다.
메타 테이블
배치 데이터 수행에 대한 이력을 관리하는 테이블이다. `schema-` 로 파일 검색시 DDL script를 확인할 수 있다.
아래의 정보를 확인할 수 있다.
- 작업이 어떤 Job Parameter로 언제 실행되었는지
- 성공한 Step과 실패한 Step은 어떤게 있었는지
- 다시 실행한다면 어디서부터 실행해야 하는지
- 테이블 종류
- BATCH_JOB_INSTANCE
- Job Parameter 별로 실행한 이력이 담긴 테이블
- BATCH_JOB_EXECUTION
- JOB_INSTANCE 가 성공/실패한 이력이 담긴 테이블
- BATCH_JOB_EXECUTION_PARAM
- Job 실행시 입력받은 Job Parameter가 담긴 테이블
Flow
Job 내부의 Step들간의 처리 흐름 제어
BatchStatus vs ExitStatus
- BatchStatus: 스프링에서 기록하는 Job | Step의 실행결과 Enum
- ExitStatus: Step의 실행 후 상태, Enum이 아님, 개발자가 임의로 설정 가능
- Flow의 API는 이 정보를 식별한다
- Spring Batch는 기본적으로 ExitStatus의 exitCode는 Step의 BatchStatus와 같게 설정
JobRepository
Spring Batch에서 사용하는 CRUD 수행하는 역할이다
- 기본 격리레벨: SERIALIZABLE
- 공식문서에서는 일반적으로 Read Committed도 해도 잘 동작한다고 한다
- 이렇게 설정한 이유는 아마
- 데이터 처리가 중요하고
- Command성 연산이 적기 때문이라고 가정하고 설계된 것 같다
관련 문서
네임스페이스 또는 제공된 `FactoryBean`을 사용하는 경우 리포지토리 주변에 트랜잭션 조언이 자동으로 생성됩니다. 이는 장애 후 재시작에 필요한 상태를 포함한 배치 메타데이터가 올바르게 유지되도록 하기 위한 것입니다. 리포지토리 메서드가 트랜잭션 방식이 아닌 경우 프레임워크의 동작이 제대로 정의되지 않습니다. create*` 메서드 속성의 격리 수준은 작업이 시작될 때 두 프로세스가 동시에 동일한 작업을 시작하려고 시도하는 경우 한 프로세스만 성공하도록 하기 위해 별도로 지정됩니다. 이 메서드의 기본 격리 수준은 `SERIALIZABLE`로 매우 공격적입니다. READ_COMMITTED`도 일반적으로 똑같이 잘 작동합니다. 두 프로세스가 이런 식으로 충돌할 가능성이 없는 경우 `READ_UNCOMMITTED`도 괜찮습니다. 그러나 create* 메서드에 대한 호출은 매우 짧기 때문에 데이터베이스 플랫폼이 지원하는 한 `SERIALIZED`로 인해 문제가 발생할 가능성은 거의 없습니다. 그러나 이 설정을 재정의할 수 있습니다. Translated with DeepL.com (free version)
Job Parameter
애플리케이션 기동 시점이 아니라 JobScope, StepScope 단위에서 실행이 된다. (Late Binding) 따라서 해당 어노테이션이 설정되어있어야 한다.
@Value("#{jobParameters['requestDate']}") String requestDate 로 선언해서 사용하면 된다.
이것을 사용하면, 한번 실행한 Job을 다시 실행하는 불상사가 없다
Chunk 처리 중 실패시 동작 with Item Stream
JdbcPagingItemReader 등의 구현체는 ItemReader, ItemStream을 구현했는데 이중 ItemStream의 기능을 통해 스프링 배치는 실패한 Job | Step 에 대해 실패 지점(근처)에서 재시작할 수 있게 보장한다
내부적으로 완료된 chunk에 대해 offset값을 ExecutionContext에 기록한다 (update메서드)
보통은 chunk size와 page size를 일치시킨다
단위 처리 기준: Chunk, PageSize
스프링 배치는 Chunk라는 크기 기준으로 데이터를 트랜잭션 처리한다
반면 읽는 것은 Reader의 구현체에 따라 다르지만
Page구현체의 경우는 PageSize 기준, Cursor구현체의 경우 1개씩 읽는 것으로 알고 있다
ItemReader
PagingItemReader
page사이즈 기준으로 한방 쿼리를 가져온 이후 1건씩 processor에게 넘긴다
- doReadPage를 통해 한방 쿼리
- doRead 메서드에서 1건씩 반환
주의 사항
데이터가 update & commit 될때, 읽어야 할 데이터가 바뀌었을 수 있는데.. 이 부분을 조심해야 한다
CursorItemReader
DB Connection을 스트림 형태로 생성, 1건씩 데이터를 가져온다
따라서 Connection SocketTimeout 시간이 다소 길어야 한다
실무 고민 포인트
- 청크 단위 처리중 에러 발생시
- 스프링 배치는 어떻게 동작하는가?
- 이때 개발자는 어떻게 조치해야 하는가?
- ItemStream 사용시에 이것을 통해서 실패한 Chunk부터 실행 가능하다
- https://jgrammer.tistory.com/entry/Spring-Batch-실패를-다루는-기술-ItemStream
- 스프링 배치가 2개 이상 동시에 돌아갈 때
- Shed Lock으로 중복 연산 방지
- Shed Lock에 대해 알아보자
- 이미 실행한 Job을 다시 실행하고 싶을때
- version={} 인자를 넘겨서 임의로 실행
기타 참고하면 좋을 자료
스프링 배치 샘플 코드
'Back-end > Spring Boot, JPA' 카테고리의 다른 글
Quiz! 다음 코드에서 JPA가 실행하는 Query는? (0) | 2024.04.04 |
---|---|
[땅굴 조사] JPA 트랜잭션이 수행되기까지 (0) | 2024.02.01 |
Spring Data Repository 확장 (짧음) (0) | 2023.12.13 |
Thread Safe하게 처리해보자 (feat. synchronized , 낙관, 비관, named) (0) | 2023.06.27 |
ArgumentResolver처리 과정 + Spring Web MVC 흐름 (0) | 2023.06.20 |
hi hello... World >< 가장 아름다운 하나의 해답이 존재한다
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!