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

Spring Batch에서 Bulk Insert로 전환하기

by applepick 2024. 2. 3.
반응형

회사에서 혼자 관리하고 있는 배치가 40개가 넘는다.... 허허...

특정 기존 배치가 너무 느려 성능 최적화를 일정을 잡고 진행하기로 했다.

 

최적화를 한 번 해본 경험이 도움이 되었는지 첫 번째로 구조적으로 문제가 없는가?를 판단했다. 처리해야 하는 데이터는 볼륨이 얼마나 되는지, 읽어오는 쿼리가 최적화가 되어있는지, 메모리 누수는 없는지 등등... 정확하게 파악해 나갔다. 특정 Step에서 대략 2500만 데이터를 읽어서 10개가 넘는 테이블에 적재해야 하는 작업이 발생했다. 이 시점에 정말 느려 최적화가 필요했다. 일단 이 Step에서 병렬처리(Parallel)로 진행하도록 변경했다. 메모리 사용량도 증가시켰다. 병렬처리의 키가 될 수 있는 데이터를 분석해서 thread를 20개로 적용해 놨다.

 

기존 wirte 하는 부분을 수도 코드로 작성해 봤다.

    public void write(List<? extends MyData> items) {
       for(MyData dwo : items) {
            sampleDQM.insertAtable(dwo.getMyData());
            sampleDQM.insertAzHisTable(dwo.getMyData());
            sampleDQM.insertBtable(dwo.getBData());
            sampleDQM.insertBzHisTable(dwo.getBData());
            sampleDQM.insertCtable(dwo.getCData());
       }
   }    
 ....

 

chuck 사이즈만큼 wirte에 할당해서 위와 같은 방식으로 insert를 진행했다. 하지만 이러한 방법은 Bulk Insert를 사용할 수 없다.

 

이유는 뭘까?

ItemWriter의 write 메서드 안에서 각 아이템을 개별적으로 처리하는 형태로 작성되어 있기 때문에, 위와 같이 for 루프를 사용하여 각 아이템을 개별적으로 처리하고 있다. 이러한 방식으로는 개별적인 Insert 쿼리가 수행되기 때문에 Bulk Insert가 이루어지지 않는다.

 

아래와 같은 방식으로 변경할 수 있다.

    public void write(List<? extends MyData> items) {
        List<MyData> aList = new ArrayList<>();
        List<MyData> bList = new ArrayList<>();
        List<MyData> cList = new ArrayList<>();

        for (MyData dwo : items) {
            aList.add(dwo);
            bList.add(dwo);
            bzList.add(dwo);
            cList.add(dwo);
        }

        // Bulk Insert 수행
        sampleDQM.insertAtableBatch(aList);
        sampleDQM.insertAzHisTableBatch(aList);
        sampleDQM.insertBtableBatch(bList);
        sampleDQM.insertBzHisTableBatch(bList);
        sampleDQM.insertCtableBatch(cList);
    }

 

mybatis의 mapper를 확인해 보면

<mapper namespace="com.example.SampleDQM">
    <!-- insertAzHisTableBatch 쿼리 -->
    <insert id="insertAzHisTableBatch" parameterType="java.util.List">
        INSERT INTO az_his_table (column1, column2, column3, ...) 
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.field1}, #{item.field2}, #{item.field3}, ...)
        </foreach>
    </insert>
</mapper>

이런 식으로 작성하면 된다.

 

Bulk insert를 사용하면 어떤 장점이 있을까?

여러 개의 레코드를 한 번에 처리하기 때문에 DB 통신 횟수가 줄어 성능이 향상될 수 있고 데이터베이스에서도 한 번에 처리하기 때문에 트랜잭션 비용을 절감할 수 있다. 간단하게 생각해 보면 각 병렬로 처리하는 쓰레드 하나가 처리해야 하는 데이터가 300만 개 정도라고 생각해 보자. 한번에 Read에서 읽어오는 데이터가 만건,  chuck 사이즈가 200이라고 생각해보자.

 

Read page 10,000

chuck size 200

한 개의 데이터를 처리하기 위해서 5개의 테이블을 insert 요청을 보낸다. chuck size만큼 처리되면 대략 1000번의 쿼리를 모아서 한번에날린다. 1만 건의 데이터를 처리하기 위해서는 DB에서는 5만개의 쿼리를 처리해야한다. 처리해야 하는 데이터가 많아질수록 쿼리의 요청 횟수가 증가된다.

 

하지만 Bulk insert를 사용하면 어떻게 될까?

200개의 데이터를 처리하기 위해서 5번의 쿼리만 요청하면 된다. 만건의 데이터를 처리하기 위해 DB에서는 250개의 쿼리만 처리하면된다. 처리해야 할 데이터가 많을수록 Bulk insert가 장점이 크다. 

 

반응형

댓글