본문 바로가기
Spring/Spring Batch

[Spring Batch] Batch 활용 목적과 Performance 개선 방법

by seoyamin 2024. 1. 24.

아래 글은 카카오 테크의 강의를 학습한 후 개인적으로 정리한 내용임을 밝힙니다. (자료 출처)

 

 

 

1. Batch 활용하기

1-1. 일괄 처리 Batch Application? 

원하는 시간에 스케줄을 걸어 대량의 데이터를 일괄적으로 처리할 때 사용한다.
Realtime 서비스에 비해 개발 부담이 적은 경우가 많다.

 

 

1-2. Batch 활용 케이스

① 일괄 생성

  • READ → CREATE → WRITE
  • 기존의 정보를 조합하여 새로운 정보를 만드는 경우

 

② 일괄 수정

  • READ → UPDATE → WRITE
  • 이미 저장된 데이터를 일괄적으로 수정하는 경우

 

③ 통계

  • SUM READ → CREATE → WRITE
  • 이미 저장된 데이터를 통계적 형식으로 집계하는 경우

 

 


2. Batch Performance 개선

2-1. 대량 데이터 READ

Batch 성능 개선의 첫 걸음은 바로 Reader 개선에 있다.
실제로, Batch 성능에서 Reader는 Writer보다 큰 비중을 차지하고 있다.
복잡한 쿼리 조건을 통해 데이터를 가져오기 때문이다.

 

2-1-1. Chunk Processing

Chunk Processing은 Batch에서 데이터를 읽는 절대적 방법으로 여겨진다.

 

소량의 데이터는 한번에 처리해도 서버에 무리가 없다. 그러나, 대량의 데이터는 한번에 처리하는 것이 서버에 큰 무리가 될 수 있다. 따라서 이를 n개 단위로 나누어 조금씩 처리하는 방법을 Chunk Processing이라고 한다.

 

'n개씩 나누어'라는 표현에서 떠오르는 친구가 있을 것이다. 바로 Pagination이다.
Chunk Processing은 Pagination Reader을 통해 데이터를 n개씩 페이지 단위로 읽어와서 처리한다.

 

그런데, Pagination Reader는 offset이 커질 수록 소요 시간이 커진다는 문제점을 가지고 있다. 
어디서부터 읽어올 것인지를 나타내는 offset 위치를 찾는 행위가 MySQL에 부담을 주기 때문이다.

 

이를 해결하기 위한 방법으로, ZeroOffsetItemReader가 있다.
이 Reader는 id 값의 하한선을 업데이트함으로써 항상 offset을 0으로 유지하는 전략을 이용한다.
결국, offset 조회 시간을 줄이는 효과가 생긴다.

 

QueryDSL과 ZeroOffsetItemReader를 함께 사용하면 편리함에 안전함을 더한 방법으로 쿼리를 구현할 수도 있다.

 

 

2-1-2. Cursor 

Cursor는 데이터가 없을 때까지 n개씩 fetch를 반복하는 방식이다.

 

Spring에서 Cursor를 지원하는 Reader에는 아래와 같은 종류가 있다.

이 중 JpaCursorItemReader는 MySQL의 Cursor 방식이 아니라, 데이터를 일단 다 읽어온 후 서버에서 Cursor하는 방식이다. 따라서 데이터 양이 많은 경우 Out Of Memory Error를 유발할 수 있다.

 

반면, JdbcCursorItemReader나 HibernateCursorItemReader는 MySQL의 Cursor 방식을 그대로 이용하여 OOM 위험성이 적다. 그러나, sql문을 String으로 이용해야 한다는 부담이 있다. 

(Kotlin + Spring 조합인 경우, exposed라는 기술을 이용하여 해당 문제를 해결할 수 있다)

 

 

 

 

2-2. 데이터 Aggregation 처리

대량의 데이터를 가공하여 통계내는 쿼리에서는 GroupBy와 Sum 함수가 자주 사용된다.

그러나 이러한 함수들은 1) 연산 과정이 쿼리에 의존하여 DB 부하 증가, 2) 데이터 누적 시 쿼리 튜닝 어려움, 3) 쿼리 튜닝을 위한 인덱스 자체가 많은 용량 차지함이라는 3가지 한계를 갖는다.


이를 해결하기 위한 방법으로, GroupBy를 사용하지 않고 직접 Aggregation 하는 것이 있다.
직접 Aggregation 하는 경우, Redis Pipeline을 활용하여 소요시간을 줄일 수 있다.

 

 

2-3. 대량 데이터 WRITE

Writer 성능 개선을 위해서는 다음 2가지 조건을 지켜야 한다.

① Batch Insert : 항상 일괄적으로 쿼리 요청을 해야 한다.
② 명시적 쿼리 : 필요한 컬럼만 Update해야 한다. 이를 위해 영속성 컨텍스트는 사용하지 않아야 한다.

 

위의 조건을 고려하면 JPA는 Batch Writer에게 적합하지 않음을 짐작할 수 있다.

적합하지 않은 3가지 이유를 함께 정리해보자.

  • JPA Dirty Checking과 영속성 관리
  • Update하는 경우, 불필요한 컬럼도 함께 Update됨
  • JPA Batch Insert 지원이 어려움

 

2-3-1. Dirty Checking과 영속성 관리

JPA는 영속성 관리를 위해 불필요한 체크 로직을 많이 소비한다. 이는 곧 성능 저하로 이어진다.

 

2-3-2. Update 시 불필요한 컬럼들도 업데이트됨

JPA는 하나의 컬럼을 업데이트하는 과정에서 불필요한 다른 컬럼 업데이트도 동시에 발생하는 한계점이 있다.
이는 곧 성능 저하로 이어진다.

 

 

2-3-3. JPA Batch Insert 지원이 어려움

Batch Insert는 쿼리를 모아서 한번에 처리하는 방식이다. 
Batch Insert를 사용하지 않으면 매 쿼리마다 Network I/O 소요 시간이 오래 걸리게 된다.

 

Spring JPA에서 Insert를 제공하긴 하지만, Id 생성 전략이 IDENTITY인 경우 Batch Insert를 지원하지 않는 문제가 있다.
이는 곧 성능 저하로 이어진다.

@Id
@GenerationValue(strategy = GenerationType.IDENTITY)
Long id;

 

 

 

[JDBC Batch Insert 구현 방법]

 

 

2-4. 효과적인 Batch 구동 환경

Spring Cloud Data Flow를 이용하면 Batch 구동을 모니터링할 수 있다.

https://spring.io/projects/spring-cloud-dataflow/

 

Spring Cloud Data Flow

Microservice based Streaming and Batch data processing for Cloud Foundry and Kubernetes. Spring Cloud Data Flow provides tools to create complex topologies for streaming and batch data pipelines. The data pipelines consist of Spring Boot apps, built using

spring.io