개발/Spring Batch

스프링 배치 개선하기 정리

Debin 2024. 9. 10.
반응형

스프링 배치 성능에 관한 아래 두 영상을 간략하게 정리한 글입니다.

 

Batch Performance 극한으로 끌어올리기

https://www.youtube.com/watch?v=2IIwQDIi3ys&t=589s

Spring Batch 애플리케이션 성능 향상을 위한 주요 팁

https://www.youtube.com/watch?v=VSwWHHkdQI4&t=1011s

 

 

Batch Performance 극한으로 끌어올리기 영상 정리

대량 데이터 READ

  • batch 성능 개선에서 제일 중요한 것은 Reader를 개선하는 것이다.
  • 일반적으로 Read의 복잡한 조건으로 인해 Write보다 성능의 더 큰 영향을 받는다.
  • 읽을 때 항상 Chunk 프로세싱이다. 예를 들어 천 만개를 1000개씩 나누어 1만번 처리한다.
  • JpaPagingItemReader, RepositoryItemReader는 대량 처리에 매우 부적합하다.
  • Limit Offset이 가지는 태생적인 한계 때문이다. Offset은 커질수록 느려지기 때문
  • 영상에서 ZeroOffsetItemReader를 도입함. pk를 기준으로 정렬하여 데이터를 가져온다. 무한 스크롤 방식과 비슷
  • 즉 limit offset이 아니라 cursor 방식을 활용하면 된다.
  • JpaCursorItemReader는 활용할 수 없다. 데이터를 모두 읽고 서버에서 직접 커서하는 방식이라 OOM을 유발할 수 있다.
  • JdbcCursorItemReader, HibernateCursorItemReader을 활용할 수 있다. MySQL의 Cursor를 활용하여 일정 개수만큼 Fetch하는 방식이라 안전하다.
  • 다들 Jdbc를 자주 사용하나 타입 안전성과 세련된 방식으로 Exposed를 제시.
  • 배치 모니터링에서 Querydsl, Exposed를 사용한 Reader가 안정적인 모습을 보인다.

 

 

결론

  • cursor 방식을 사용하자.
  • offset 방식이 아닌 cursor를 활용하자.
  • 타입 안정성을 원하면 Kotlin에서는 Querydsl, Exposed를 활용할 수 있고 Java는 Querydsl 을 활용하자.

 

데이터 Aggregation 처리

  • 통계를 만들 때 배치로 개발한다면 group by와 sum을 활용한 쿼리를 생각한다.
  • 위 사고 방식은 데이터가 적을 때는 합리적인 방식이지만 데이터가 많아지면 그렇지 않다.
  • 여러 테이블을 조인하고 group by하면 잘못된 실행 계획이 등장하며, 쿼리 튜닝이 까다로워진다.
  • join, group by, sum을 활용한 쿼리를 사용하면 아래와 같은 문제가 생긴다.
    • 연산 과정이 쿼리에 의존적이라 DB 부하가 증가한다.
    • 데이터 누적으로 인해 데이터 중복도(카디널리티)가 변경되고 쿼리 실행 계획이 변경되면서 쿼리 튜닝 난이도가 증가한다.
    • 쿼리 튜닝을 위한 과도한 인덱스를 추가하며 저장 용량이 증가하고 결국 insert, update 속도가 느려진다.
  • 결론은 Group By와 Sum을 포기하고 애플리케이션에서 Aggregation을 진행하는 방식을 채택
  • 그러나 애플리케이션에서 50만 개의 데이터를 위한 메모리 할당은 불가능하다. OOM이 발생할 수 있다.
  • 결론은 새로운 아키텍쳐가 필요한데, 이는 Redis를 활용하는 방식
    • 천만 개의 데이터를 1000개로 나누어 만 개의 청크를 만든다.
    • sum 연산 요청을 통해 redis가 sum 연산을 진행
    • 반복해서 합산을 진행하면 레디스에 50만개의 데이터가 들어가고
    • 이를 데이터 최종 저장소에 반영한다.

  • Aggregation Tool로 Redis를 선택한 이유
    • 연산 명령어 지원(메모리 수준에서 합산)
    • 50만개를 저장할 수 있는 넉넉한 메모리. 보통 수십 기가바이트에서 수백 기가바이트로 레디스를 구축한다.
    • 인메모리 데이터베이스이므로 빠른 저장이 가능하기 때문(영구 저장 X)
  • Redis를 도입해도 해결되지 않는 문제
    • 네트워킹의 레이턴시는 해결되지 않음
    • 1000만번 sum 요청은 1000만번의 네트워크 I/O와 같다
    • 요청 한 번당 1ms * 1000만번이면 3시간이다.
    • Redis Pipeline으로 처리해서 이 문제를 해결했다.
    • Redis Pipeline은 다수의 커맨드를 한 번에 묶어서 처리할 수 있는 방식이다.
    • 한 개의 청크에서 한번씩 연산 요청을 해 천만번의 네트워킹을 만 번으로 줄여서 어그리게이션을 진행
    • Spring Data Redis에서는 Redis Pipeline을 지원하지 않아 별도 라이브러리를 개발

대량 Write

  • Read할 때부터 Projection을 사용해 더티체킹을 사용 X (더티체킹과 영속성은 배치 insert에서 성능 저하가 크므로 사용 X, 큰 성능 저하)
  • update할 때 불필요한 컬럼도 update하면 동적 쿼리를 생성하므로 Dynamic Upate 사용 X (소폭 성능 저하)
  • JPA도 Batch Insert를 지원하지만 ID 전략을 IDENTITY로 하게 되면, JPA 사상과 맞지 않으므로 Batch Insert를 지원하지 않는다. (큰 성능 저하)
  • Batch Insert를 반드시 사용해야한다.
  • JPA를사용하지 않고 batch i
  • nsert(JDBC), Exposed Batch Insert를 추천

Batch 구동 환경

  • Batch  Tool로 크론탭, 젠킨스, 에어 플로우, 루이지 등의 환경을 떠올릴 수 있다.
    • Batch 스케쥴Toole은 실행 요청, 스케줄, 배치 관리, 워크 플로우 관리, 모니터링, 히스토리 관리와 같은 일을 수행
  • 기존 Toole의 아쉬운점
    • 자원 관리의 어려움
    • 배치 상태 파악의 어려움
      • 배치에서는 동작 하나하나가 매우 깊다.
      • 대부분 스케쥴 Toole에서는 로그를 볼 수 있지만 로그 정보가 매우 빈약
      • 서비스 상태를 로그로 판단하는 것은 전혀 시각적이지 않음
    • 스프링 클라우드 데이터 플로우를 도입함
      • 데이터 수집, 분석, 데이터 입/출력과 같은 데이터 파이프라인을 만들고 오케스트레이션
      • 데이터 파이프라인 종료(스트림, task(batch))
    • 오케스트레이션 (k8s와 완벽한 연동으로 batch 실행 오케스트레이션)
    • 모니터링 스프링 배치와 완벽한 호환을 통해 유용한 정보 시각적으로 모니터링

 

Spring Batch 애플리케이션 성능 향상을 위한 주요 팁

첫 번째 방법

  • Processor에서 외부 API 조회를 진행함 (응답 속도 150ms)
  • Processor에서는 요소들을 단건으로 처리하기 때문에 Chunk Size만큼 Network I/O가 발생하고 대기하니 느려질 수 밖에 없다.
  • 개선을 위해 프로세서 구간을 제거하고 등급 조회 API를 Bulk로 처리하는 것으로 수정
  • Bulk 처리는 Network I/O를 멀티 스데르 기반으로 처리했다는 의미 (RX 기반 멀티스레드로 병렬 처리)
  • 단순히 멀티 스레드를 늘린다고 성능이 증가하는 것은 아님

두 번째 방법

  • 단건 update를 in절을 활용해 I/O를 최소화함. (in update)
  • in절을 활용한 업데이트가 불가능한 상황이 발생
    • Jdbc Execute Batch를 활용
    • 쿼리 여러개를 한번에 묶어서 전송

  • Exposed 기반 Execute Batch를 활용해서 해결이 가능함

 

이상으로 포스팅을 마칩니다.

반응형

댓글