본문 바로가기
성능 개선

[테스트 환경] 벌크 쿼리(Bulk Query) 사용하기

by sangyunpark99 2024. 10. 7.
테스트 환경에서
벌크 쿼리를 사용해
성능을 개선한 경험을
담았습니다. 🙆🏻‍♂️

 

개선할 점

테스트 코드에서 데이터베이스(MySQL)에 데이터를 저장할 때, 반복문을 사용하여 데이터 저장을 수행했습니다. 하지만 반복문이 실행되는 횟수만큼 쿼리가 실행되기 때문에, 예를 들어 100만 건의 데이터를 저장할 경우, 100만 번의 쿼리가 실행됩니다. 이는 저장할 데이터와 쿼리 실행 횟수가 1:1 비율로 증가하여 매우 비효율적이라고 생각합니다.

 

 

개선한 방법

벌크 쿼리(Bulk Query)를 사용하여 만 건의 데이터를 한 번의 쿼리로 처리했습니다. 이를 통해 데이터 저장 시 쿼리 실행 횟수를 줄이고 성능을 크게 향상시킬 수 있었습니다. 

 

 

벌크 쿼리 사용전 코드

@Test
void not_use_bulkInsert() throws Exception{
    //given
    EasyRandom easyRandom = PostFactory.get(3L, LocalDate.of(2024,1,1), LocalDate.of(2024, 2,2));

    //when
    List<Post> posts =IntStream.range(0, 10000)
             .mapToObj(i -> easyRandom.nextObject(Post.class)).toList();

    stopWatch.start();
    for(Post post : posts) {
        postRepository.save(post);
    }
    stopWatch.stop();
    //then

    System.out.println("실행 시간 : " + stopWatch.getTotalTimeSeconds());
}

 

EasyRandom은 설정해준 임의의 값의 범위 내에서 랜덤한 문자, 숫자와 같은 값으로 임의의 엔티티를 만들어주는 역할을 합니다.

데이터(만건)를 저장하는데 걸린 시간은 로컬 환경 기준으로 총 85s이 걸립니다.

테스트 경과시간

 

벌크 쿼리 사용 후 코드

@SpringBootTest
public class PostInsertTest {

    @Autowired
    private PostRepository postRepository;

    @Test
    @DisplayName("대용량 데이터 넣기")
    void bulkInsert() throws Exception{
        //given
        EasyRandom easyRandom = PostFactory.get(3L, LocalDate.of(2024,1,1), LocalDate.of(2024, 2,2));

        StopWatch stopWatch = new StopWatch();

        //when
        List<Post> posts = IntStream.range(0, 10000)
                .mapToObj(i -> easyRandom.nextObject(Post.class)).toList();

        stopWatch.start();
        postRepository.bulkInsert(posts);
        stopWatch.stop();

        //then
        System.out.println("실행 시간 : " + stopWatch.getTotalTimeSeconds());
    }
}

 

데이터(10,000개)를 저장하는데 걸린 시간은 1s 24ms 입니다.

 

벌크 쿼리를 사용하니 약 83s 더 빠르게 데이터를 저장했습니다.

 

 

벌크 쿼리 코드(Repo)

public void bulkInsert(List<Post> posts) {
    String sql = String.format(
            """
            insert into %s (user_id, contents, created_date, created_at)
            values(:userId, :contents, :createdDate, :createdAt)
            """, TABLE_NAME);

    SqlParameterSource[] params = posts
            .stream()
            .map(BeanPropertySqlParameterSource::new)
            .toArray(SqlParameterSource[]::new);

    namedParameterJdbcTemplate.batchUpdate(sql,params);
}

 

INSERT INTO VALUES 쿼리문을 사용해서 벌크 삽입 코드를 구현했습니다.

 

 

주의할 점

application.properties에 아래와 같이 datasource.url에 rewriteBatchedStatements=true 옵션을 설정 해주어야 합니다.

rewriteBatchedStatements 옵션 설정

적용방법은 다음과 같습니다.
jdbc:mysql:://DB주소:포트/DB이름?rewriteBatchedStatements=true

 

 

결론

많은 수의 데이터를 저장할때, Bulk insert를 사용해서 데이터를 저장하는 것이 훨씬 빠릅니다.