본문 바로가기

JPA

JPA - 기본 키 매핑 IDENETITY vs SEQUNECE

MySQL 8.0 innoDB 스토리지 엔진 환경에서 JPA를 사용했을 때 기준으로 포스팅을 작성합니다. 포스팅은 자바 ORM 표준 JPA 프로그래밍과 개인 프로젝트를 통해 얻은 경험을 바탕으로 작성하였습니다.

 

목차

1. 들어가기 앞서 가볍게 JPA를 통해 기본키 설정

2. IDENETITY 전략 및 주의사항

3. SEQUENCE 전략과 allocationSize

4. IDENTITY vs SEQUENCE

 


JPA는 @Id를 통해 기본 키를 할당할 수 있습니다. 아래와 같이 기본키를 직접 생성할 수 있습니다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class StudyProduct {

    @Id @Column(name = "study_product_id")
    private String id;
    private String name;

    public StudyProduct(String name) {
        this.id = UUID.randomUUID().toString();
        this.name = name;
    }
}

 

뿐만 아니라 @GenerateValue를 통해 기본 키 생성 전략을 설정하여 자동으로 기본키를 생성할 수 있습니다.

 

@Id @GeneratedValue(strategy = IDENTITY)
private Long id;

 

자동 생성 방식은 데이터베이스 종류에 따라 지원하는 종류도 상이하고 데이터베이스 버전마다 다른 듯 합니다. 예를 들면 자바 ORM 표준 JPA 프로그래밍 책이 발간은 MySQL 8.0이 등장하기 이전이기 때문에 MySQL 에서는 SEQUNECE 전략을 사용할 수 없다고 합니다. 하지만 MySQL 8.0을 사용할 경우 SEQUENCE를 사용할 수 있는 것 같아요.

 

대표적인 자동 할당 방식은 아래와 같습니다.

- IDENTITY : 기본 키 생성을 데이터베이스에 위임

- SEQUENCE : 데이터베이스 스퀀스를 사용해서 기본 키를 할당

- UUID : RFC 4122 Universally Unique IDentifier

 

IDENETITY 전략 및 주의사항


MySQL 환경에서 IDENETITY 전략 사용 시, auto_increment가 적용됩니다. IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본 키 값을 조회할 수 있습니다. 그래서 1차 캐시에 엔티티를 보관하기 위해서는 기본 키를 얻기 위한 추가 조회가 필요하다. 하지만 JDBC3에 추가된 Statement.getGeneratedKeys()를 통해 데이터 저장과 동시에 생성된 기본 키 값도 얻어올 수 있습니다. 하이버네이트는 이 메소드를 사용해서 데이터베이스와 한 번만 통신한다.

 

주의 사항

IDENETITY 전략은 앞서 말했듯 데이터베이스에 저장한 후에 식별자를 구할 수 있다. 그렇기 때문에 쓰기 지연이 동작하지 않는다.

 

사실 1차 캐시의 범위는 트랜잭션 단위이기 때문에 쓰기 지연이 크게 매력적이게 느껴지지 않을수도 있다. 하지만 한 트랜잭션에서 많은 레코드를 넣어야 할 일이 존재하면 쓰기 지연을 통해 성능을 향상시킬 수 있는 중요한 특징입니다.

 

 

SEQUENCE 전략과 allocationSize


SEQUNECE 전략을 사용하려면 아래와 같이 @GeneratedValue 에 SEQUENCE를 명시하면 됩니다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "group_task")
public class Task {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "group_task_pk")
    private Long id;

 

이후 데이터베이스 시퀀스를 매핑해주면 된다. @SequenceGenerator를 명시하지 않으면 기본 값을 바탕으로 데이터베이스에 매핑한다.

@SequenceGenerator(name = "group_task_seq", initialValue = 1, allocationSize = 50)

 

allocationSize는 SEQUENCE 전략에서 최적화와 관련있기 때문에 추가 정리를 해보려고 합니다.

 

먼저 allocationSize를 설정하는 이유를 알기 위해서 SEQUNECE 전략이 데이터베이스에 어떻게 동작하는지 알아야 합니다. SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요합니다. 이로 인해 데이터베이스와 2번 통신한다. 

 

1. 식별자를 구하기 위해 데이터베이스 시퀀스를 조회

 

2. 조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장

 

 

이처럼 SEQUENCE 전략은 식별자를 구하는 것이 선행되며 이 과정에서 데이터베이스와 통신해야 한다. 이 과정을 줄이기 위해서 JPA는 allocationSize를 사용한다. 예를 들어 allocationSize 값이 50이면 현재 seq 값에서 50개까지는 메모리에서 식별자를 할당한다.

 

이후 메모리에서 50번 기본 키를 할당하면 seq를 업데이트 한다.

 

책에 따르면 이 최적화 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있따고 한다. 반면 데이터베이스에 직접 접근해서 데이터를 등록하면 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해두어야 한다. 이로 인해 INSERT 성능이 중요하지 않으면 allocationSize을 1로 설정하면 된다고 한다.

 


IDENTITY vs SEQUENCE

사용하는 데이터베이스에 따라 다르겠지만 MySQL에서 두 전략을 모두 사용할 수 있습니다. 그리고 위 내용을 바탕으로 장단점을 정리하면 아래와 같습니다.

 

IDENTITY

장점 : INSERT 시 데이터베이스와 한 번 통신으로 끝낼 수 있다.

단점 : 쓰기 지연을 사용할 수 없다.

 

SEQUENCE

장점 : 쓰기 지연을 사용할 수 있다.

단점 : allocationSize를 설정하긴 하지만 INSERT를 위해 데이터베이스와 두 번 통신해야 한다. 

 

allocationSize를 매우 크게 가져가면 SEQUENCE 단점을 줄일 수 있는 것 아니냐는 생각을 해보기도 했지만 그렇게 된다면 사용되는 메모리가 많아질 것 같다.

 

결국 INSERT 성능이 중요한 경우에는 SEQUENCE 를 고려해볼만 하지 않을까? 라는 결론이 났다.

 

무론 SEQUENCE 전략만 사용한다고 성능 최적화가 바로 일어나는 것은 아닐 수도 있다. 이때는 batch_size를 함께 사용해야 한다. 

spring.jpa.properties.hibernate.jdbc.batch_size = 50

 

이렇게하면 쓰기 쿼리를 트랜잭션 단위에서 50개 씩 모와서 보낸다. 아래처럼 말이다.

 

 

마지막으로 어쩔 수 없이 IDENETITY 전략을 사용할 수 밖에 없는 환경이 있을수도 있다. 그럴 때는 JDBC template을 사용해서 배치 쿼리를 짜서 INSERT 최적화를 수행할 수도 있습니다.

'JPA' 카테고리의 다른 글

JPA - 단방향 @OnetoMany 연관관계 설정 시 단점  (0) 2023.05.21
JPA - 값 타입 컬렉션 주의 사항  (0) 2023.05.21
JPA - @JoinColumn, mappedBy  (0) 2023.05.12
JPA - 2차 캐시 알은 채 하기  (0) 2023.05.06
JPA - 값 타입  (0) 2023.04.07