Back-End/Spring

[Spring] Spring Jdbc - batchUpdate()를 사용한 bulk Insert 최적화

HMHA 2023. 2. 9. 14:02
300x250
반응형

Spring Data JPA의 saveAll()메서드는 인자 값으로 받은 entity들을 저장하는 메서드이다.

실제로 실행해보면 여러 엔티티들에 대한 insert 쿼리가 각각 한 번 씩 차례대로 나가는 것을 볼 수 있다.

엔티티의 수가 적으면 상관없지만, 10,000건, 100,000건 처럼 많아지면 엔티티 1 : insert 1 방식은 부담이 될 수 있다.

 

인터넷을 검색해보면 hibernate.jdbc.batch_size: 50 처럼 한 번에 여러 건을 insert하는 벌크 연산이 많이 소개되어 있다.

이 방법을 사용해도 좋지만, 여기서는 Spring JPA가 아닌 JDBC를 사용한 보다 빠르고 강력한 연산인 batchUpdate()를 소개하고자 한다.

 

Bulk Insert

bulk insert는 여러 insert쿼리를 합쳐서 보낼 수 있는 방법이다.

3건의 데이터를 insert 한다고 하자.

INSERT INTO table1 (col1, col2) VALUES (val11, val12);
INSERT INTO table1 (col1, col2) VALUES (val21, val22);
INSERT INTO table1 (col1, col2) VALUES (val31, val32);

이렇게 하면 개별 insert이고,

 

INSERT INTO table1 (col1, col2) VALUES
(val11, val12),
(val21, val22),
(val31, val32);

이렇게 하면 batch insert다. batch insert가 개별 insert에 비해 훨씬 효율적임을 쉽게 알 수 있다.

 

SaveAll() - batchUpdate()

Spring Data JPA에 정의되어 있는 saveAll()메서드는 기본적으로 개별 insert를 제공한다.

 

그러나 대량의 엔티티를 insert해야 하는 경우, batch insert를 사용해야 한다.

새로운 리포지토리를 생성해 Jdbc를 사용해서 saveAll()메서드를 재정의해보자.

 

@Repository
@RequiredArgsConstructor
public class FoodJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    private int batchSize = 10000;

    public void saveAll(List<Food> items) {
        int batchCount = 0;
        List<Food> subItems = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            subItems.add(items.get(i));
            if ((i + 1) % batchSize == 0) {
                batchCount = batchInsert(batchCount, subItems);
            }
        }
        if (!subItems.isEmpty()) {
            batchCount = batchInsert(batchCount, subItems);
        }
        System.out.println("batchCount: " + batchCount);
    }

    private int batchInsert(int batchCount, List<Food> subItems) {
        jdbcTemplate.batchUpdate("INSERT INTO food (`food_id`, `name`) VALUES (?, ?)", new BatchPreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, subItems.get(i).getId());
                        ps.setString(2, subItems.get(i).getName());
                    }
                    @Override
                    public int getBatchSize() {
                        return subItems.size();
                    }
                });
        subItems.clear();
        batchCount++;
        return batchCount;
    }
}

Food라는 객체를 Food 테이블에 batch insert로 저장한다고 했을 때의 예시이다.

 

batchSize 변수를 통해 배치 크기를 지정하고, 전체 데이터를 배치 크기로 나눠서 Batch Insert를 실행하고, 자투리 데이터를 다시 Batch Insert로 저장한다.

 

-> 실제로 Spring Data JPA와 비교했을 때, batchUpdate()가 압도적인 성능 우위를 보여준다.

 

+) 참고로 MySQL의 경우 datasource에 rewriteBatchedStatements=true 옵션을 추가해 주어야 한다.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
    username: admin
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver

 

 

출처 : https://gksdudrb922.tistory.com/m/154

 

[Spring] Spring Jdbc - batchUpdate()를 사용한 bulk Insert 최적화

Spring Data JPA의 saveAll()메서드는 인자 값으로 받은 entity들을 저장하는 메서드이다. 실제로 실행해보면 여러 엔티티들에 대한 insert 쿼리가 각각 한 번 씩 차례대로 나가는 것을 볼 수 있다. 엔티티

gksdudrb922.tistory.com

 

300x250
반응형