JPA

JPA Entity ID 타입 선택시 후보들

BBiRaRiRo 2024. 5. 27. 08:29

 

GenerationType identity, sequence, table

identity

  • 키 생성 방식을 데이터베이스에게 위임하는 방식
  • 영속성 컨텍스트에 저장하기 위해 데이터베이스에 insert를 전달하여 PK를 얻어 오기에 insert에서 쓰기 지연이 되지 않는다

sequence

  • 유일한 값을 순서대로 생성하는 데이터베이스 오브젝트를 사용한다.
  • 시퀀스를 제공하는 오라클, PostgreSQL, DB2, H2 데이터베이스에 사용
  • insert시 데이터베이스 시퀀스를 사용하여 PK 를 얻기에 쓰기 지연이 가능하다

table

  • 키 생성 전용 테이블을 만들어서 시퀀스 전략처럼 동작하는 방식이다
  • 전용 테이블을 만들어서 처리하는것이기때문에 시퀀스를 제공하지 않는 데이터베이스도 가능하다
  • table는 키를 얻기 위해 select를 사용 후 다음 값으로 증가하기 위해 update 쿼리도 사용한다 이로 인해 부하가 크다
💡 이 방식들은 분산 시스템에서 좋은 품질로 구성하기 힘들다 만약 작은 규모의 프로젝트라면 identity 로도 충분하다.

UUID(RFC 4122 or RFC 9562)

36글자를 사용한 유일성을 보장하는 ID 방식 이며 분산환경에서의 사용성 증가 하고
애플리케이션에서 생성하여 저장하기에 데이터베이스의 부하 감소를 기대할 수 있다.

매 초 10억 개의 uuid를 100년에 걸쳐서 생성할 때 단 하나의 uuid가 중복될 확률은 50%이다.

출처 : 위키피디아 - Universally unique identifier

 
그렇지만 UUID를 사용 시 32글자이기에 데이터 크기와 인덱스가 커지게 되어 용량 증가 및 INNO DB engine의 인덱스 성능 감소 가 발생한다.

💡 uuid를 char로 저장하면 32바이트로 데이터를 많이 차지하게 된다. 바이너리로 저장하여 16바이트로 처리 가능 하다.
 💡 키의 길이가 길어질수록 읽어야 하는 페이지의 개수가 달라진다
key의 길이가 4byte 일 때와 6byte 일 때를 비교해 보면
한 페이지에 15KB 저장 공간을 사용할 때(INNO DB 기본 페이지 크기 16KB - 1KB(전체를 사용 안 하고 여유를 둔다))
4byte 키 1페이지 의 value 수는 (15KB/4) 3750 개이고
6byte 키 1페이지 의 value 수는 (15KB/6) 2500 개이다.
3000개의 데이터를 조회 시 4byte 키는 페이지1개 , 4byte키는 페이지 2개를 조회해야 한다.
이러한 이유 때문에 RDB를 사용할 때 문자보단 가짓수가 많은 정수 타입을 선호한다.

GenerationType.UUID를 사용하여 쉽게 ID 를 UUID 로 생성 가능하다.

@Entity
public class Entity {
	@Id
	@GeneratedValue(strategy = GenerationType.UUID)
	private UUID id;
}

---------------------
5c5645c4-9b05-45cf-a5f2-295e903fc8df
846f6eee-5722-490b-8508-6121f7e8fc51
5d7a64b0-a98b-4f9c-8c34-10932875f159
3b95e8eb-a251-46e9-9974-8d841388a02d
87973616-501e-4bf4-aab1-a5140f6be5e2
007d9d31-907c-4d8c-9f85-081c265c4eb1
5b2e34d8-575f-4792-82c8-5badca1719b1

GenerationType.UUID를 사용하는경우 RFC 4122 의 UUID 를 사용하는 것이며

public enum GenerationType { 

    /**
     * Indicates that the persistence provider must assign
     * primary keys for the entity by generating an RFC 4122
     * Universally Unique IDentifier.
     */
    UUID
}

RFC 4122를 ID로 사용하는 경우
ID 가 순서 없이 생성되기에 아래와 같은 문제가 발생한다.

  • 저장 시 Clustered index의 조정으로 인한 부하 증가
  • UUID로 저장 시 최신의 데이터가 서로 다른 인덱스 페이지에 존재하기에 인덱스 성능 감소

RFC 9562의 UUID는
RFC 4122의 UUID 아같은 36글자이지만 앞자리 48 bit 부분에 타입스탬프를 추가하여 RFC 4122 UUID의 문제를 해결하였다

018f6fb1-c724-764a-bc00-32c430eb6fdd
|-----------|
 time stamp

타임 스탬프를 이용하여 정렬된 순서로 생성되기에 Clustered index에 적합하다.
RFC 9562의 UUID를 사용할 때
이미 시스템에서 RFC 4122의 UUID를 사용 중이었다면 UUID v6로 변경하고 신규 시스템이면 UUID v7을 사용한다

UUIDv6 is a field-compatible version of UUIDv1 (Section 5.1), reordered for improved DB locality. It is expected that UUIDv6 will primarily be implemented in contexts where UUIDv1 is used. Systems that do not involve legacy UUIDv1 SHOULD use UUIDv7 (Section 5.7) instead.

https://datatracker.ietf.org/doc/html/rfc9562

implementation 'com.github.f4b6a3:uuid-creator:5.3.2'
@Entity
public class Entity {
	@Id
	private UUID id;
	
	@PrePersist
	public void createSn() {
	    //this.id = UuidCreator.getTimeOrdered(); /** UUID v6 **/
      this.id = UuidCreator.getTimeOrderedEpoch(); /** UUID v7 **/
	}
}

----

/** UUID v6 **/   
1ef10cc2-faa7-6c1a-9ae7-efbd7470c2fe
1ef10cc2-fadf-6e8b-9ae7-efbd7470c2fe
1ef10cc2-fae4-6cac-9ae7-efbd7470c2fe
1ef10cc2-faec-61dd-9ae7-efbd7470c2fe
1ef10cc2-faf0-6ffe-9ae7-efbd7470c2fe
1ef10cc2-faf8-652f-9ae7-efbd7470c2fe
1ef10cc2-fafd-6350-9ae7-efbd7470c2fe

/** UUID v7 **/
018f6fb1-c724-764a-bc00-32c430eb6fdd
018f6fb1-c73a-77f7-b44c-32999bea1ee7
018f6fb1-c73d-7b04-89b8-27abd2287170
018f6fb1-c740-760c-8179-f7c59042aa2d
018f6fb1-c743-7901-8762-9dddf7193e92
018f6fb1-c746-761b-95e7-636a5f241801
018f6fb1-c749-7042-b15f-080980b4cf86
 01AN4Z07BY      79KA1307SR9X4MV3

ULID(Universally Unique Lexicographically Sortable Identifier)

GitHub - ulid/spec: The canonical spec for ulid
UUID v4 (RFC 4122)의 문제를 UUID v7(RFC 9562)가 시간으로 풀어낸 것처럼
ULID 도 같은 방법으로 풀어낸 방식이며 UUID 보다 짧은 26자를 사용한다.

 01AN4Z07BY      79KA1307SR9X4MV3

|----------|    |----------------|
 Timestamp          Randomness
   48bits             80bits

트위터의 스노플레이크

GitHub - twitter-archive/snowflake at snowflake-2010
트위터의 독창적인 ID 생성 기법인 스노플레이크는 64Bit의 크기를 사용하여 여러 절로 분할해서 사용한다.
1Bit의 아직까진 사용하지 않는 여유 공간인 사인비트
41Bit를 사용하여 기원 시각(epoch) 기준 타임스탬프
5Bit를 사용하여 총 32개의 데이터 센터를 지원가능한 데이터센터 ID
5Bit 를 사용하여 데이터 센터당 32개의 서버 지원가능한 서버 ID
각 서버에서 ID를 생성할 때마다 1씩 증가하며 1밀리 초마다 0으로 초기화되는 일련번호
최대 1024개의 서버를 지원하며 41Bit로 표현가능한 값은 69년이므로 69년 후에는 기원 시각을 변경할 필요가 있다.

TSID

TSID는 스노플레이트와 ULID를 합쳐 만들어진 자바 라이브러리이다.

💡 ULID 문자열 길이가 26자이며 RFC 9562처럼 앞에 타임스탬프가 있다.

 
https://github.com/f4b6a3/tsid-creator
TSID는 RFC 9562의 이점을 모두 가지고 있으며 추가적으로 8바이트를 사용하기에 공간 효율성이 좋다.

38352658567418867
38352658567418868
38352658567418869
38352658567418870
38352658567418871
38352658567418872
38352658567418873
38352658567418874
38352658573940759 < millisecond changed
38352658573940760
38352658573940761
38352658573940762
38352658573940763
38352658573940764
38352658573940765
38352658573940766
         ^      ^ look
                                   
|--------|------|
   time   random

TSID는 사용하는 노드 수에 따라 1ms에 생성가능한 고유 ID 수 제한이 바뀐다.
1ms 기준 노드 256개는 최대 16384개
1ms 기준 노드 1024개는 최대 4096개
1ms 기준 노드 4096개는 최대 1024개
 

결론

단순 프로젝트 = identity
일반 프로젝트 = RFC 9562 의 UUID
특수한 프로젝트 = TSID