배치에서 여러 개의 하위 Step에 OOM이 발생했다. 원인이 무엇일까? 해당 배치에 Heap Dump를 분석했다. 특정 배치가 뒤로 갈수록 메모리 사용률이 90%가 넘는 것을 확인했다. 다시 확인해 봤다. 특정 Step에서 병렬처리하기 위해 partitioner를 사용해서 병렬처리를 하고 있는데 이 방식이 잘못된 것을 인지했다. 보편적으로 병렬처리 할 Setp이 실행되기 전 partition을 구하고 병렬처리하는 것이 일반적이다. 해당 배치는 병렬처리하는 Step이 많아 파티션을 공통으로 사용하도록 설계되었다.
public class StepUtil {
public static Map<Long, String> context;
~~~~~하위 코드작성~~~~~~
}
이런 정적(static) 클래스로 파티션이 될 모수를 미리 구해놓은 다음 각각 Flow에서 해당 context에 값의 여부에 따라 병렬처리 여부를 확인 후 처리하는 방식으로 되어있었다. 이 방법은 잘못되었다.
정적(static) 클래스는 클래스 로딩 시점에 메모리에 로딩되며, 해당 클래스의 인스턴스는 애플리케이션 라이프사이클 동안 메모리에 유지된다. 이로 인해 아래와 같은 문제점이 발생한다.
1) 배치 라이프사이클 반영: @JopScope나 @StepScope를 통해서 자원할당, 해제 및 효율적인 배치시스템을 만들 수 있다. 이러한 장점을 누리기 어렵다. 해당 스코프를 무시하고 항상 heap 영역에 메모리를 유지시킨다.
파티션을 정적 클래스에 올려놓으면 이 파티션의 상태는 애플리케이션의 라이프사이클 동안 메모리에 유지된다. 따라서 시간이 지날수록 파티션의 상태에 대한 메모리 사용량이 누적될 수 있으며, 이로 인해 OOM 문제가 발생한다.
2) 스레드 안전성 문제: 정적 클래스에 상태를 저장하면 병렬 처리 중 다수의 스레드에서 해당 상태에 접근하게 된다. 이 경우 스레드 간의 경쟁 조건 및 동기화 문제가 발생할 수 있다.
스프링 배치에서는 병렬 처리를 위한 Partitioner, Reader, Writer 및 Step을 사용하여 병렬성을 관리하고 상태를 효과적으로 처리할 수 있도록 지원한다. 일반적으로 파티션을 동적으로 생성하고 관리하는 방식을 사용해야하며, 파티션 간에 공유되는 상태를 최소화하고 스레드 안전성을 고려해야 해야 한다.
위와 같은 문제점 정리한 뒤 팀원들에게 전달하고 작업을 진행했다. 각 Step에서 각각 파티션을 구하도록 리팩터링 한 뒤 util class를 삭제했다. 파티션의 모수는 동일하기에 Partitioner 상속 class를 생성해서 각자 해당되는 Step에서 사용하도록 변경했다. 더 이상 메모리누수(OOM)가 발생하지 않았다.(뿌듯)
'혼자 공부하는 것들 > Spring' 카테고리의 다른 글
Spring Batch에서 Bulk Insert로 전환하기 (0) | 2024.02.03 |
---|---|
Spring Batch(+Mybatis)에서 Commit하기 전 실행된 SQL 문의 개수를 확인하고싶으면? (4) | 2023.07.15 |
서버가 죽었다!.... 502....(100 % of root file system is in use. 0 MB free.) (0) | 2023.05.06 |
회사에서 Spring batch 짜면서 삽질한거 정리 (1) | 2023.04.05 |
@Component는 어떻게 동작하는 걸까? (0) | 2023.01.24 |
댓글