공부내용공유

MongoDB + Spring Batch 간단히 알아보기 본문

Spring/Spring Batch

MongoDB + Spring Batch 간단히 알아보기

forfun 2024. 9. 22. 20:49

서론


현재 진행중인 프로젝트에서 대규모 데이터 처리를 하기위해 Spring Batch를 사용하게 되었다.

 

다른 팀원이 맡고 있는 프로젝트에서도 Spring Batch를 사용하고 있지만 MySql을 사용하고 있는 프로젝트이고 MongoDB를 대상으로 사용하는 프로젝트는 없는 상태이다, 그래서 Spring Batch를 공부한 내용과 MongoDB와 사용할 때 알면 좋은 점을 간단히 정리하고자 이 글을 작성하였다.

 

 

본론


 

이 글의 목차는

  • Spring Batch를 왜쓸까?
  • Spring Batch 간단한 소개
  • Mongo와 사용할 때 알면 좋은 것들

로 구성될 예정이다.

 

 

Spring Batch를 왜 쓸까?

 

 

어플리케이션을 개발하다 보면 대규모의 데이터를 처리해야하는 기능이 생기기 마련이다. 예를 들자면

 

  • 전체 상품 거래 통계
  • 전체 사용자 순위 갱신
  • 기간이 지난 데이터들 삭제

 

등등.. 도메인에 따라 데이터는 많지만 단순한 처리가 필요하거나 데이터도 많고 처리해야 하는 과정도 복잡한 요구사항들이 있을 것이다. 이러한 대규모 데이터 처리를 할 때는 단순히 비즈니스 적인 요구사항을 제외하고도 신경쓸 부분이 많다.

 

 

  1. 대용량 데이터이기에 chunk, page 단위로 차례 차례 처리해야 한다.
  2. 단위 별로 트랜잭션 처리가 고민되어져야 한다.
  3. 데이터를 처리하면서 처리 과정, 결과에 대한 로깅 혹은 메타 데이터 저장 및 통계 기능이 필요하다.
  4. 처리 도중 예외가 발생할 경우 처리가 필요하다.
  5. 성능상 혹은 요구사항을 위해 멀티 스레딩을 사용하여 빠르게 처리해야 한다.

 

우리는 비즈니스 로직에만 집중해도 바쁜데 이 모든걸 고려하기 힘들다, 마치 일반 api 서버를 만들 때 Request를 파싱하고 요청에 해당하는 데이터를 가져와 다시 응답해주는 과정을 Spring MVC에게 위임하는 것처럼 Spring Batch를 통해 우리는 비즈니스 로직에만 집중할 수 있다.

 

Spring Batch 간단한 소개

 

 

MongoDB와 사용할 때 알면 좋은 점들을 설명하기 전에 그와 관련된 내용들을 간략하게 알아보자.

 

 

출처 : https://docs.spring.io/spring-batch/reference/domain.html

 

 

jobRepository

 

위 사진을 통해 Spring Batch의 기본적인 개념들을 알 수 있다. 아까 Spring Batch 사용 이유에서

데이터를 처리하면서 처리 과정, 결과에 대한 로깅 혹은 메타 데이터 저장 및 통계 기능이 필요하다.

 

 

라고 얘기를 했었는데 사진에서 확인 가능한 JobRepository가 배치 잡을 하면서 생기는 기타 정보들을 저장하는 Repository이다. Spring Batch를 사용하게되면 이 정보들을 저장하기 위한 테이블을 알아서 만들어주고 저장을 한다.

 

출처 : https://jojoldu.tistory.com/326?category=902551

 

 

각 테이블에 대한 정보는 향로님의 블로그에 굉장히 정리가 잘되어 있는 읽어보면 좋을 것 같다!

 

 

Job

 

사진에서 볼 수 있는 Job은 하나의 배치 작업을 의미한다. 즉 앞서 예시로 들었던 상품 거래 통계, 순위 갱신 등등이 각각의 Job이 되는 것이다. 그리고 그 Job은 1개 이상의 Step으로 나누어진다.

 

 

이를테면 상품 거래 통계의 Job은

  • 상품 거래 통계 계산
  • 통계 내역 각 판매자에게 전송
  • 갱신된 통계 내역 기반으로 판매자 순위, 아이템 순위 조정
  • 변경된 순위에 따른 아이템들 처리

 

 

대충 이런식으로 하나의 요구사항에서 필요한 각각의 데이터 처리사항을 Step으로 나눌 수 있다.

@Bean
    public Job Job() {
        return jobBuilderFactory.get("example")
                .start(step1())
                    .on("FAILED") 
                    .to(step3()) 
                    .on("*") 
                    .end() 
                .from(step1()) 
                    .on("*") 
                    .to(step2()) 
                    .next(step3()) 
                    .on("*") 
                    .end() 
                .end()
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                ...
                .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("conditionalJobStep2")
                ...
                .build();
    }

    @Bean
    public Step step3() {
        return stepBuilderFactory.get("conditionalJobStep3")
                ...
                .build();
    }

 

Step을 구성할 때는 각 순서대로 차례로 흘러가는게 기본이지만 위 코드와 같이 실패할 경우 어떤 스텝으로 가게도 분기 처리가 가능하다.

 

 

각 Step에다가 각각의 처리할 로직을 작성하면 되는데 보통의 경우 로직을 처리하기 위해 데이터를 읽어오고, 처리하고, 다시 저장을 해야 할 것이다.

 

위 사진에서 볼 수 있듯이

  • Reader
  • Processor
  • Writer

을 각각 구현하여 Step에서 사용할 수 있다. Reader, Processor, Writer를 구성할 때도 PageReader, CursorReader, AsyncReader 등등 고려할 사항이 많으니 자세한 내용은 공식문서나 다른 블로그를 참고하면 좋을 것 같다.

 

 

Mongo와 함께 사용할 때 알면 좋은 것들

 

 

1. Job repository는 아직 RDB만 가능

공식문서에 따르면 v5.1부터 사용이 가능하긴하다. 다만 아직 정식 버전이 아닌 실험성인 기능이라 실 제품에다가 사용하기는 힘들 것 같다.(개인 플젝이면 시도해도 좋을듯 하다 (job info 저장하려고 RDB 따로 붙이는게 기분 나쁘다.))

 

spring-batch-experimental 레포에서 자세한 내용이 확인 가능하다.

 

 

2. 멀티 스레드 환경에서 안전한가?

 

 

 

 

mongoTemplate 공식 문서에서 확인이 가능한데 일단 MongoTemplate은 thread-safe하다. 또 spring batch에서 지원해주는 MongoItemReader

 

The implementation is thread-safe between calls to
AbstractItemCountingItemStreamItemReader.open(ExecutionContext),
but remember to use saveState=false if used in a multi-threaded client (no restart available).

 

 

문서 설명에서 볼 수 있듯이 thread-safe 하다. 여기서는 MongoItemReader 문서 링크를 올려놨는데 deprecated되었다, 대안책들은 아래에서 다루겠다.

 

 

3. MongoPageItemReader, MongoCursorItemReader

 

5.3 부터는 MongoItemReader를 삭제할 예정이라고 한다, 대신에 5.12부터 PageItemReader, CursorItemReader 사용이 가능하다. Mongo의 경우 페이징을 할 때 데이터가 많을 경우 성능에 문제가 생긴다.

Paging Costs

Unfortunately skip can be (very) costly and requires the server to walk from
the beginning of the collection, or index, to get to the offset/skip
position before it can start returning the page of data (limit).
As the page number increases skip will become slower and more cpu intensive,
and possibly IO bound, with larger collections.
Range based paging provides better use of indexes
but does not allow you to easily jump to a specific page.

 

 

대충 데이터 쭉 나열하고 offset 만큼 데이터 건너 뛰어 가져오느라 10만번 째 데이터라면 이전 10만개의 데이터 건너뛰고 이는 성능적으로 문제가 있을 수 있으니 정렬과 인덱스를 잘 사용하는 방식을 추천한다는 내용이다.

 

 

위 코드는 PageItemReader의 데이터를 읽어오는 메서드로 코드에서 확인할 수 있듯이 위에서 언급했던 페이징 방식으로 데이터를 가져오기 때문에 너무 대량의 데이터라면 PageItemReader가 아닌 Custom Reader나 Cursor Reader를 고려하는 것이 좋을 것 같다.

 

 

결론


아직 개발을 시작하지 않아서 그렇게 많은 팁이 있지는 않지만 앞으로 개발하면서 팁들을 알게 되면 해당 글에 추가하거나 다른 글에 모아서 한번 더 정리를 할 예정이다!