JPA IDENTITY 방식은 Batch Insert가 비활성화 된다.

Jang990·2024년 9월 1일
0

Insert 성능 개선

목록 보기
2/3

Batch Insert 비활성화?

하이버네이트 문서에는 기본키 생성에 IDENTITY 방식을 사용하면 Hibernate가 JDBC 수준에서 batch insert를 비활성화한다고 나와있습니다.

지금은 뜬금없이 왜 비활성화되었는지 의문이 들 수 있는데, JPA의 기본키 생성 방식을 다시 한 번 살펴봅시다.

Bulk Insert란

Bulk Insert란 Insert 쿼리를 한번에 처리하는 것을 의미
INSERT INTO test_entity (something) VALUES(111),(222),(333),(444),(555)


JPA 기본키 생성 방식

JPA 기본키 생성 방식 하위의 내용들은 김영한님의 책. 자바 ORM 표준 JPA 프로그래밍의 내용입니다.

JPA의 기본키 생성 전략 (1~3은 자동할당)

  1. IDENTITY - DB에게 기본키 생성 위임 ex)MySQL - Auto-Increment
  2. SEQUENCE - DB 시퀀스 사용 ex) Oracle - Sequence
  3. TABLE - 키 생성 테이블 이용
  4. 직접할당 - 애플리케이션에서 직접 할당

자동할당 전략이 다양한 이유는 DB 벤더마다 지원하는 방식이 다르기 때문입니다.
예를 들어 오라클 DB는 시퀀스를 제공하지만, MySQL의 경우 AUTO_INCREMENT 기능을 제공합니다.

DB 종류도 많고 기본키를 만드는 방법도 다양하기 때문에 GenerationType.AUTO는 선택한 DB 방언에 따라 자동할당 방식 중 하나를 자동으로 선택합니다.
예를 들어 오라클의 경우 SEQUENCE를 선택합니다. 이건 조금씩 달라질 수 있기 때문에 잘 알아보고 쓰는게 좋습니다.

SEQUENCE

시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트입니다.

SEQUENCE 전략의 저장 과정

  1. em.persist() 호출
  2. DB 시퀀스를 사용해 식별자를 조회
  3. 조회한 식별자를 엔티티에 할당
  4. 엔티티를 영속성 컨텍스트에 저장

이렇기 때문에 IDENTITY 전략과 다르게 쓰기 지연이 가능합니다.

오라클 시퀀스 확인하기

저는 주로 MySQL을 썼기 때문에 시퀀스가 익숙하지 않습니다.
그래서 오라클의 시퀀스 관련 코드들을 적어놓겠습니다.

CREATE TABLE BOARD (
	ID BIGINT NOT NULL PRIMARY KEY,
    DATE VARCHAR(255)
)

-- 시퀀스 생성문
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

JPA에서 시퀀스를 직접 생성할 수 있는데 다음과 같이 하면 됩니다.

/*
시퀀스 DDL
CREATE SEQUENCE [sequenceName]
START WITH [initialValue] increment by [allocationSize]
*/

@Entity
@SequenceGenerator(
        name = "BOARD_SEQ_GENERATOR", // 시퀀스 생성기 이름
        sequenceName = "BOARD_SEQ", // 실제 DB의 시퀀스
        initialValue = 1, allocationSize = 1)
public class Board {
    @Id
    @GeneratedValue(strategy = SEQUENCE, generator = "BOARD_SEQ_GENERATOR") // 방금 등록한 시퀀스 생성기 선택
    // 식별자 값은 BOARD_SEQ_GENERATOR 시퀀스 생성기가 할당함
    private Long id;
}

allocationSize

여기서 중요한 것이 allocationSize입니다.
allocationSize는 시퀀스 한 번 호출에 증가하는 수로 하이버네이트의 기본값은 50입니다.
이 기본 설정값 50은 insert 성능 최적화를 위함입니다.

50개의 엔티티를 저장 예시를 비교하면 와닿을 것입니다.

allocationSize이 1일 때

  1. 식별자를 구하려고 DB 시퀀스 조회 ex) 시퀀스를 1증가시키고 1 사용
  2. 조회한 식별자 1을 엔티티에 할당
  3. 식별자를 구하려고 DB 시퀀스 조회 ex) 시퀀스를 1증가시키고 2 사용
  4. 조회한 식별자 2를 엔티티에 할당
  5. ... 50까지 반복.

allocationSize이 50일 때

  1. 식별자를 구하려고 DB 시퀀스 조회 ex) 시퀀스를 50증가시키고 1~50 사용
  2. 조회한 식별자 1을 엔티티에 할당
  3. 조회한 식별자 2을 엔티티에 할당
  4. ... 50까지 DB 통신없이 할당 가능

allocationSize를 크게 잡으며 DB 시퀀스에 접근하는 횟수가 훨씬 줄었습니다.
이렇게 Insert 성능을 향상시킨 것입니다.

allocationSize를 설정하면 DB에는 어떻게 저장되는지 보고 싶다면 시퀀스 allocationSize 정리글을 참고합시다.

IDENTITY

엔티티가 영속 상태가 되려면 식별자가 반드시 필요합니다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 DB에 저장해야 식별자를 구할 수 있습니다. 그래서em.persist()를 호출하는 즉시 INSERT SQL이 DB에 전달됩니다. 따라서 이 전략은 쓰기 지연이 동작하지 않습니다.

IDENTITY 전략의 저장 과정

  1. em.persist() 호출
  2. INSERT INTO 쿼리 호출
  3. INSERT INTO 결과 받아와서(JDBC - Statement.getGeneratedKeys()) ID를 엔티티에 할당
  4. 엔티티를 영속성 컨텍스트에 저장

즉 save를 호출할 때마다 insert into 쿼리가 나가게 되는 것입니다.


이제 기본키 생성에 IDENTITY 방식을 사용하면 Hibernate가 JDBC 수준에서 batch insert를 비활성화한다고 나와있는 이유가 어느정도 이해가 되실겁니다.

근데 Auto-Increment가 +1이 아니라 +50이 되게 설정해서 시퀀스처럼 쓰면 되지 않을까요?
그럼 MySQL에서는 Table 방식을 쓰면 되지 않을까요?

다음 글에서는 위의 내용들을 직접 적용해보면서 무엇이 문제인지 파악해보겠습니다.

profile
공부한 내용을 적지 말고 이해한 내용을 설명하자

0개의 댓글