batch insert는 단건 insert가 아닌, insert를 모아서 하는 multi insert를 의미한다.
jpa에서 객체를 데이터베이스에 저장할 때 save() 대신 saveAll()을 한다고해서 멀티 insert가 되는건 아니었다. 별도의 설정이 필요했다.
설정 추가
데이터베이스별로 설정 방법이 다르다.
(Oracle은 못찾았다..)
// 공통 설정
spring.jpa.properties.hibernate.jdbc.batch_size=1000
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
// mysql은 아래 설정 추가
spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true
// postgre는 아래 설정 추가
spring.datasource.hikari.data-source-properties.rewriteBatchedInserts=true
- batch_size: insert/update를 실행할 크기 설정
- order_inserts와 updates: 엔티티별로 insert와 update를 정렬하여 실행시키는 옵션
- rewrite 옵션: insert 구문을 재작성하는 옵션
주의 사항
1) id 채번 방식이 GenerationType.IDENTITY이면 batch insert가 불가능하다.
GenerationType.SEQUENCE 또는 GenerationType.TABLE로 설정 변경이 필요하다.
GenerationType.TABLE은 PK를 별도 테이블로 관리하는 건데 관리 할 테이블이 늘어나기 때문에 번거롭다.
그래서 sequence 전략을 추천하는데, 만약 이를 지원하지 않는 DB라면(ex. Mysql) JPA가 아니라 jdbc를 사용하여 bulk insert를 수행하는게 속도 측면에서 더 우수하다고 한다.
- GenerationType.IDENTITY 전략은 데이터 입력 시 엔티티를 먼저 저장하고 식별자를 조회해서 할당하는 방식이다.
- GenerationType.SEQUENCE 전략은 데이터 입력 시 sequence를 먼저 조회하고(sequence 조회 쿼리 발생) 엔티티에 할당하여 저장한다.
2) id 채번 방식 → batch 채번으로 변경
id 채번을 sequence 타입으로 변경한다고 해서 batch insert가 빠른 속도로 수행되는 것은 아니다.
insert는 배치로 수행하지만 id를 채번할 때는 하나 씩 채번한다면 의미가 없기 때문이다.
그래서 id도 batch size만큼 미리 채번하도록 하는 설정이 필요하다.
id batch 채번 설정
1) hibernate의 GenericGenerator를 사용하여 Optimizer를 직접 지정하여 사용
pooled 또는 pooled-io optimizer를 사용하면 시퀀스를 increment_size만큼 채번하여 메모리에 올려둔다.
즉, 한번의 쿼리로 1000개의 시퀀스를 가지고 오는 것이다.
optimizer 설정 참고 - https://techblog.woowahan.com/2663/
// entity class
...
public class Molecule {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "uniprot_source_seq")
@GenericGenerator(
name = "uniprot_source_seq",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "uniprot_source_seq"),
@Parameter(name = "optimizer", value = "pooled"),
@Parameter(name = "initial_value", value = "1"),
@Parameter(name = "increment_size", value = "1000")
}
)
private Long id;
...
}
Oracle DB 사용 시 위 설정으로 실행했을 때 DB의 increment value 값과 @Parameter로 설정한 increment value값이 다르다는 에러가 발생했고, 끝내 해결하지 못했기때문에 다른 방법을 찾았다. ㅠㅠ
(Oracle db에서 sequence를 삭제하고 새로 생성할 때 increment by 1000;을 해도 db의 increment가 1이라는 동일한 에러 발생)
2) id batch 채번 외부 라이브러리 사용
batch insert를 적용하는 시점(Oracle DB를 사용할 때)에는 1번에 대한 해결 방법으로 외부 라이브러리를 사용하였다.
- 라이브러리 설명 - https://vladmihalcea.com/hibernate-batch-sequence-generator/
- github - https://github.com/vladmihalcea/hibernate-types
의존성 추가 및 설정
사용중인 hibernate 버전에 맞춰서 적용한다.
implementation group: 'com.vladmihalcea', name: 'hibernate-types-55', version: '2.17.0'
@GenericGenerator(
name = "uniprot_seq",
strategy = "com.vladmihalcea.hibernate.id.BatchSequenceGenerator",
parameters = {
@Parameter(name = "sequence", value = "uniprot_seq"),
@Parameter(name = "fetch_size", value = "1000")
}
)
실행 쿼리
SELECT uniprot_seq.nextval FROM dual CONNECT BY rownum <= 1000;
장점
- DB의 sequence increment를 바꾸지 않아도 된다. → sequence increment 1로 유지 가능
결과
약 5천건의 데이터 입력 성능 60배 향상
- batch insert 적용 전 - 3분 소요
- 적용 후 - 3초 소요
'spring & java' 카테고리의 다른 글
[Spring] 스프링 부트 핵심 가이드 - API 작성 기초 (4) | 2023.10.29 |
---|---|
[Spring] 스프링 부트 핵심 가이드 - Spring 기초 지식 (0) | 2023.10.22 |
[Spring] Spring AOP 적용 (0) | 2023.06.19 |
[Java] 비동기 + multi threading 구현 (0) | 2023.03.01 |
[Spring] Spring Security + JWT (7) | 2023.02.06 |