[Spring + MySQL] batch size 조정을 통한 대용량 데이터 삽입 성능 향상

YJoo·2022년 11월 2일
0

서버 개발 일기

목록 보기
2/5
post-thumbnail

배경

프로젝트를 진행하며, 수십만건에 이르는 데이터를 수집해야 할 일이 생겼다. 처음에는 수십만건, 후에는 지속적으로 업데이트해야 하는 상황이었다. 처음에는 그냥 db 모델링을 하고, api를 작성해 진행했지만 약 11000건을 수집하는 데 16분이 걸려, 너무 느리다고 생각해서 구글링을 통해 문제를 해결했다. 마침 우아한 형제들 블로그에 관련 포스트가 있어 참고했다.

하이버네이트 배치

배치 작업은 대량의 작업을 한번에 처리하는 경우를 말한다. 하이버네이트 배치는 jdbc에서 제공하는 배치기능을 활용하는데, 설정한 배치 갯수에 도달할때까지 addBatch를 호출하고, 설정한 배치 갯수에 도달하면 executeBatch를 호출해 addBatch를 통해 추가된 쿼리를 재조합해 DB로 한번에 전송하게 된다. 여러개의 쿼리를 한번에 모아서 처리하기 때문에 쿼리를 하나씩 수행할 때에 비해 실행 속도가 향상된다.
프로젝트에 직접 적용을 해본 결과, 원래는 1분당 평균 약 790개의 물건을 저장할 수 있었는데, 적용 후에는 1분당 평균 약 3000개의 물건을 저장할 수 있었다.

적용하기

application.yml에 아래 코드만 추가해주면 배치 설정을 할 수 있다.
batch_size는 한번에 insert/update할 크기이다.

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
        	batch_size: 1000

MySQL

MySQL JDBC의 경우, rewriteBatchedStatements=true 옵션을 추가해야 적용이 된다고 한다. 따라서 application.yml에 url을 수정해줘야 한다.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hibernate_batch?rewriteBatchedStatements=true

ID가 자동증가 값일 때

프로젝트에 위 내용을 적용시켰지만, 처음에는 실행 시간이 거의 줄어들지 않았다. 이유를 찾아보니, ID가 자동증가 값일 때(ex. GenerationType.IDENTITY일 때)는 배치 적용이 안된다고 한다. Vlad Mihalcea의 포스트를 통해 그 이유를 알 수 있었다.

Whenever an entity is persisted, Hibernate must attach it to the currently running Persistence Context which acts as a Map of entities. The Map key is formed of the entity type (its Java Class) and the entity identifier.
-> Hibernate는 entity를 그 클래스와 id를 이용해 Persistence Context에 Attatch한다.

For IDENTITY columns, the only way to know the identifier value is to execute the SQL INSERT. Hence, the INSERT is executed when the persist method is called and cannot be disabled until flush time.
-> IDENTITY의 경우, id를 알려면 insert 쿼리를 먼저 수행해야 한다.

For this reason, Hibernate disables JDBC batch inserts for entities using the IDENTITY generator strategy.
-> 이런 이유로, IDENTITY 생성전략에서는 batch insert가 적용되지 않는다.

id 변경

현재 삽입하고자 하는 entity의 id는 IDENTITY 전략 생성이었고, 때문에 batch insert가 동작하지 않았다. 때문에 id를 생성자에서 uuid 랜덤값으로 생성하는 방식으로 변경했다. 그 결과, batch insert가 제대로 작동하는 것을 확인할 수 있었고, 위에서 말했듯 1분당 평균 약 790개 -> 1분당 평균 약 3000개로 비약적 향상을 볼 수 있었다.

결론

대용량 데이터 삽입/수정이 필요한 경우, batch size 설정을 통해 훨씬 효율적으로 수행 가능하다. 이 프로젝트의 경우, 3배 이상 시간이 감소됐다. MySQL의 경우에는 rewriteBatchedStatements=true 옵션이 추가적으로 필요하고, IDENTITY 생성전략의 경우에는 batch insert가 불가능하다.

profile
https://github.com/Y-Joo

0개의 댓글