본문 바로가기
혼자 공부하는 것들/Spring

회사에서 Spring batch 짜면서 삽질한거 정리

by applepick 2023. 4. 5.
반응형

출처: 데브경수

요즘 배치 작업을 하면서 항상 벽에 막힌다... 데이터 양이 많아 일반적인 처리방식으로는 평생 안 끝날 것 같다... 그래서 DB 테이블을 업무에 맞춰서 파티션을 나누기도 하고 인덱스를 어떻게 잡아야 하는지도 많이 고민했었다. 혼자 끙끙 앓고 있었는데 유튜브에서 아주 좋은 영상을 봤다.

 

https://www.youtube.com/watch?v=2IIwQDIi3ys 

직접 겪어보니 더 재미있었다.

 

병렬 처리 할 때는 파티션을 나누는 기준부터 각 데이터 순서를 보장해야 하는 작업은 별도로 청크방식으로 작업하고 순서를 보장 안 해줘도 되는 작업은 병렬처리를 통해 속도를 개선하고 있다. 특히 병렬 처리 할 경우 스레드 안정성도 고려해야 하고 서버 자원의 대한 이해가 있어야 한다... 암튼... 성능 최적화가 더 필요하겠지만... 일하면서 궁금했던 것들을 정리하려고 한다. 스프링 배치에서 가장 중요한 게 Reader 부분인 것 같다. 이 부분을 중점적으로 쿼리 최적화를 했었다. Reader 쿼리 성능에 변천사가 있었다.

6시간 -> 20분 -> 10분 초반

개선하면서 엄청난 시간을 단축시켰다. 조금 뿌듯했다!  

 

병렬 처리할 때 Partitioning을 사용하는데 이런 오류를 마주쳤다. 

JobExecutionException: Partition handler returned an unsuccessful step at org.springframework.batch. core.partition. support. PartitionStep.

 

이 오류는 Spring Batch에서 Partitioning 기능을 사용하는 경우 발생할 수 있는 오류라고 합니다. Partitioning은 대량의 데이터를 작은 덩어리로 분할하고 각 덩어리를 별도의 스레드에서 병렬 처리하는 기능을 제공합니다.

Partitioning을 사용하면 병렬 처리를 효과적으로 수행할 수 있지만, 각 파티션 처리 과정에서 오류가 발생할 경우 전체 작업이 실패하게 됩니다. 따라서 Partitioning 기능을 사용하는 경우 각 파티션에서 발생하는 오류를 처리하는 방법을 고려해야 합니다.

해당 오류는 Partitioning 과정에서 PartitionHandler가 반환한 Step이 실패한 경우 발생합니다. PartitionHandler는 각 파티션을 처리하기 위한 Step을 생성하고, 이를 실행하는 역할을 합니다. 만약 PartitionHandler가 반환한 Step에서 오류가 발생할 경우 이 오류가 JobExecutionException으로 던져지고, 전체 Job이 중단됩니다.

따라서 오류를 해결하기 위해서는 파티션에서 발생한 오류를 처리하는 방법을 고려해야 합니다. 파티션 처리 과정에서 발생한 오류를 로그에 기록하거나, 오류를 발생시킨 데이터를 따로 처리하거나, 오류를 무시하고 다음 작업을 수행하는 등의 방법을 고려할 있습니다. 또한 PartitionHandler에서 반환한 Step 오류를 발생시키지 않도록 파티션 처리 과정을 구성해야겠다....

 

skip 처리방식

스프링 배치로 대용량 데이터를 처리하는 경우, 데이터의 일부분에 대해 오류가 발생할 수 있습니다. 이 경우, 해당 데이터는 처리를 건너뛰고 다음 데이터로 진행할 수 있습니다.

스프링 배치에서는 Skip 기능을 제공하여 이러한 오류가 발생하면 처리를 건너뛰고 로그에 오류를 기록합니다. 이를 통해 처리를 중단하지 않고 계속 진행할 수 있습니다.

Skip 동작을 사용하려면 스프링 배치 작업을 구성할 때 StepBuilderFactory를 사용하여 Step을 정의하고, SkipPolicy와 SkipListener를 구현해야 합니다. SkipPolicy는 예외가 발생했을 때 처리를 건너뛸지 여부를 결정하고, SkipListener는 스킵이 발생했을 때 로그를 남기거나 통계를 추적하는 등의 작업을 수행합니다.

SkipPolicy SkipListener 구현하여 스프링 배치에서 Skip 동작을 사용할 있습니다.

 

회사에서는 customer Listener를 만들어놔서 예외처리된 건들과 처리된 것들을 로그로 잘 남기고 있다. step 별로 걸린 시간, skip 된 건들 등등... 필요한 데이터를 잘 조회할 수 있었다. 특히 멀티 스레드로 병렬처리한 것도 깔끔하게 조회할 수 있다. 로깅을 잘 남기는 것이 진짜 중요한 것 같다. 해당 Step별로 걸리는 시간을 분석하고 어느 부분이 오래 걸리는지 어떻게 개선해야 할지 고민하는 재미가 있었다.

나중에 구성방식을 한 번 까봐야겠다. AOP를 잘 사용한 표본인 것 같다. 현재 로그를 파일로 저장하여 사용하고 있는데 배치 한 번 처리하게 되면 몇 백 기가가 로그 데이터로 쌓이게 되는데 나중에 비용절감하려면 어떻게 해야 할까? 한 번 고민해 봐야겠다.

 

noSkip의 처리방식

noSkip() 메서드는 스프링 배치에서 Skip 기능을 비활성화하는 메서드입니다. Skip 기능을 비활성화하면 발생하는 예외에 대해 처리를 건너뛰지 않고, 바로 예외가 발생하면 Step을 중지시키게 됩니다.

noSkip() 메서드는 SkipLimit을 초과했을 때 Step을 중지시키는 기본 동작에서 특정 예외에 대해 Skip 기능을 비활성화하는 역할을 합니다. 즉, noSkip() 메서드에 전달된 예외 클래스를 SkipPolicy에서 건너뛰지 않고 처리합니다.

예를 들어, noSkip(FileNotFoundException.class) 같이 설정하면, FileNotFoundException 발생하면 처리를 건너뛰지 않고, 예외를 발생시킵니다. 이를 통해 특정 예외가 발생할 경우, 처리를 중단하고 예외를 던지도록 설정할 있습니다. 예외가 발생하면 해당 Step은 실패하게 됩니다. 스프링 배치에서는 Step에서 처리 중인 예외가 발생하면 StepExecution의 status가 BatchStatus.FAILED로 변경됩니다. 이는 Step이 성공적으로 완료되지 못하고 중단되었음을 나타냅니다.

따라서 예외를 처리하는 방법에 따라 Step의 상태가 결정됩니다. Skip 기능을 사용하여 예외를 처리하면 해당 예외가 발생하더라도 Step은 계속해서 실행됩니다. 그러나 예외를 던져서 처리하지 않는다면, 해당 Step은 실패하게 됩니다.

Step 실패는 Job 실행에 영향을 미칩니다. Job Step들의 실행 결과를 바탕으로 실행 상태를 결정하기 때문에, 하나의 Step 실패하면 Job 전체가 실패로 처리될 있습니다. 따라서 스프링 배치에서는 예외 처리에 대한 신중한 검토가 필요합니다.

 

MyBatisCursorItemReader MyBatisPagingItemReader 차이점

MyBatisCursorItemReader와 MyBatisPagingItemReader는 모두 MyBatis를 사용하여 데이터를 읽어오는 Spring Batch의 ItemReader 구현체입니다.

MyBatisCursorItemReader는 커서를 사용하여 한 번에 모든 데이터를 가져온 다음, 해당 데이터를 처리합니다. 이 방식은 작은 데이터 집합에 적합하며, 대규모 데이터 집합에서는 메모리 문제를 일으킬 수 있습니다.

반면에 MyBatisPagingItemReader는 페이지 단위로 데이터를 처리합니다. 쿼리 결과의 일부를 한 번에 가져오므로 대규모 데이터 집합에서도 처리할 수 있습니다. 또한, 페이징 처리를 사용하므로 메모리 문제가 발생하지 않습니다.

따라서, MyBatisCursorItemReader 작은 데이터 집합에서 성능이 우수하며, MyBatisPagingItemReader 대규모 데이터 집합에서 메모리 문제를 방지하면서 처리할 있는 장점이 있습니다

 

 

오류 발생 정리

java. lang. RuntimeException: java. sql.SOLException: JDBC-12007:Requested cursor |1778356) was not found

하나의 배치 Step에서 페이징처리 방식이 아닌 cursor로 되어있었다. 각각의 장단점을 따지고, 데이터 성격을 분석하여MyBatisCursorItemReader를 MyBatisPagingItemReader 방식으로 전환해서 오류를 해결했다.

이 오류는 배치에서 사용되는 커서(Cursor)가 사라졌을 때 발생합니다. 배치 작업 데이터베이스 서버나 네트워크 문제로 인해 커서가 끊기거나, 커서가 사용 가능한 시간이 초과되어 끊기는 경우에 발생할 있습니다. 해당 오류를 해결하기 위해서는 데이터베이스나 네트워크 연결 문제를 해결하거나, 커서가 사용 가능한 시간을 늘리는 등의 조치를 취해야 합니다.

 

SkipLimitExceededException이 발생하면 어떤 식으로 롤백되는 걸까?

SkipLimitExceededException이 발생하여 스킵이 일어나면 해당 스텝에서 처리된 청크 단위에 대한 롤백이 일어납니다. 따라서 스킵이 발생한 청크의 모든 아이템에 대한 처리가 롤백됩니다. 지금 방식은 청크방식으로 구현했기 때문에 기본적으로 청크 단위로 트랜잭션이 처리됩니다. 청크는 트랜잭션 단위로 묶이기 때문에, 스킵이 발생하여 롤백이 일어날 때에도 해당 청크에 대한 트랜잭션이 롤백됩니다.

예를 들어, commit-interval 10으로 설정된 청크 단위로 100개의 아이템을 처리하는 스텝이 있다고 가정해보겠습니다.  스텝에서 스킵 리미트가 5 설정되어 있고, 10개의 아이템을 처리하는 과정에서 스킵이 6 발생한다면, 10개의 아이템을 처리하는 청크 단위에 대한 트랜잭션이 롤백됩니다. ,  스텝에서 처리된 100개의 아이템 중에서 10개의 아이템에 대한 처리가 롤백되는 것입니다.

반응형

댓글