본문 바로가기
실무

[JPA/MySQL] saveAll() 쓰면 쿼리 하나로 나가는 거 아니었어? / JPA에서 Bulk Insert 처리해보기

2023. 11. 10.

Bulk Insert란?

  • INSERT 쿼리를 한번에 처리하는 것
  • MySQL에서는 아래처럼 Insert 합치기 옵션을 통해 성능을 비약적으로 향상할 수 있다.
INSERT INTO person (name) VALUES
	('name1'),
	('name2'),
	('name3');

 

Hibernate의 Bulk Insert

saveAll()

  • save() 메소드 내에서는 영속성 컨텍스트에서 해당 엔티티의 ID를 가져오고, ID가 null인지 아닌지를 판단하여 해당 엔티티가 새로운 엔티티인지 판단한다.
    • MySQL 사용시에는 대체로 아이디 생성 전략을 IDENTITY로 설정하는데, 이는 아이디 관리를 MySQL에 위임하는 방식이다. (MySQL 내부적으로 AUTO INCREMENT가 일어난다.)
  • saveAll()은 내부적으로 save()를 순차적으로 호출하게 구현되어 있다.
    • 그렇지만 save()를 반복문으로 호출하는 경우보다 속도가 빠르다. 그 이유는 save() 또는 saveAll()@Transactional로 감싸져있어 메소드가 호출될 때 프록시를 거쳐서 수행되는데, save()save()가 수행될 때마다 해당 과정이 필요한 반면에 saveAll()은 한번만 거친 뒤에 같은 인스턴스 내에서 save()가 여러번 수행되는 것이기 때문이다.

결론적으로 saveAll()은 실제로 단건 삽입이며, 저장개수만큼 쿼리가 호출된다.

그러나 아래 옵션들을 통해 성능을 향상시키거나, multi-row insert로 수행되게 할 수 있다.

 

batch 처리 옵션

  • Hibernate에서 hibernate.jdbc.batch_size를 설정하면, 내부적으로 jdbc의 addBatch() 메서드를 통해 지정한 개수만큼의 쿼리를 모아서 한번에 보낸다.

→ 위 쿼리처럼 multi-row insert가 아니라 단순히 네트워크 호출을 줄이기 위해 묶어서 처리할 뿐이다.

 

rewrite 옵션

  • JdbcUrl에 rewriteBatchedStatements=true 설정(MySQL 기준)을 하면 Connector 레벨에서 insert 구문을 재작성하여 multi-row insert가 수행된다.
  • 단, 아이디 생성 전략이 IDENTITY라면 불가능하다.
    • JDBC 레벨에서 batch insert를 차단한다. IDENTITY 전략을 사용하면 DB에 아이디 생성을 위임하기 때문에 저장할 데이터마다 일일히 DB에 접근해서 ID를 구해야 하기 때문이다.
    → 아이디 생성 전략을 SEQUENCE나 TABLE로 변경하거나, native 쿼리로 직접 수행해야 한다.

 

MySQL에서 bulk insert 사용하기

1. 아이디 생성 전략 변경

  • TABLE
    • 수많은 테이블에 대한 아이디 테이블을 별도로 관리하는 것은 복잡하며, 이미 저장된 데이터들을 마이그레이션하는 것은 굉장히 부담스러운 작업이다.
    • 또한 동일하게 Bulk insert가 지원되는 환경에서 auto increment 방식이 더 성능이 좋다.
  • SEQUENCE
    • MySQL은 SEQUENCE 전략을 지원하지 않기 때문에 테이블 전략으로 시퀀스를 흉내낼 수 있다.

 

2. JdbcTemplate 사용하기

  • String으로 native query를 직접 작성해야 하기 때문에 변경에 대한 영향을 크게 받으며, type-safe하지 않고, 컴파일 레벨에서 문제를 발견할 수 없다.

 

3. QueryDsl-SQL 사용하기

  • 팀에서 사용하던 QueryDsl-JPA가 아닌 별도의 QueryDsl-SQL이라는 라이브러리를 추가해 사용해야 한다.
    • QueryDsl-JPA와 달리 QClass 생성 과정이 복잡하고, 부가 설정이 너무 많이 필요하므로 잘 사용하지 않는다.

 

결론

  • 지속적으로 대량의 insert가 발생하지 않는 이상 multi-row insert까지 적용해야할 일은 많지 않다. Hibernate 레벨의 설정 만으로도 대부분의 상황에서는 충분히 성능 요구사항을 만족한다.
  • 아래 과제에서는 데이터 초기 로드 시에 수만건의 데이터가 insert되고, 그 이후로는 주로 update가 이루어지기 때문에 사실상 multi-row insert까지 필요 없다. 그러나 다양한 기술을 사용하여 문제 해결 방안을 적용해보기 위해 JdbcTemplate을 통한 bulk insert를 구현했다.

 


Reference

댓글