1. UUID (Universally Unique IDentifier) ??

범용 고유 식별자는 네트워크 상에서 중복되지 않는 식별자를 만들기 위한 표준 규약이다. GUID(전역적으로
고유 식별자)라고도 부르며 RFC4122 로 지정되어 있다. UUID는 아래와 같은 규칙을 기반으로 생성된다.

  • UUID 는 128bit(16 octec) 수이다. 32개의 16진수로 표현되며, 하이픈(-)을 포함해 총 36개 문자로 구성되어 있다.
  • UUID 는 8-4-4-4-12 형태로 총 5개의 그룹이며 하이픈(-) 을 기준으로 구분되어 있다.
이름 길이(byte) 길이(hex) 길이(bits) 내용
time_low 4 8 32 시간의 low 32bit 를 부여하는 정수
time_mid 2 4 16 시간의 middle 16bit 를 부여하는 정수
time_hi_and_version 2 4 16 최상위 비트에서 4bit 는 version, 12 bit는 시간의 high
clock_seq_hi_and_res_clock_seq_low 2 4 16 최상위 비트에서 1-3 bit 는 UUID 레이아웃 형식
13-15 bit는 clock sequcne
node 6 12 48 48bit 는 node_id

 

source : https://www.techtarget.com/searchapparchitecture/definition/UUID-Universal-Unique-Identifier

 

2. Time based UUID

[1] Time Based UUID version

  • UUID version 1 : gregorian epoch 기반의 time-based version (ordered X)
  • UUID version 6 : gregorian epoch 기반의 time-ordered version
  • UUID version 7 : Unix Epoch timestamp 기반의 time-ordered version 
Time-based UUID structure
00000000-0000-v000-m000-000000000000
|1-----------------------|2-----|3--------------|

1: timestamp
2: clock-sequence
3: node identifier

 

[2] Time Based UUID 의 장점

RDB clustered index rebalancing : MySQL 은 PK 를 기반으로 clustered index 를 생성한다. clustered index 는 PK 를 기반으로 정렬된다. 만약 정렬되지 않은 값이 삽입될 경우, 이후 인덱스의 데이터 페이지를 다시 재정렬해야 하는 경우가 발생한다. 이를 index rebalancing 이라 부르며, 운영 환경에서 index rebalaning 이 이루어지는 경우 오히려 성능이 떨어질 수 있는 위험이 있다.

 

 흔히 사용하는 UUID version4 를 사용하는 경우 데이터를 삽입하는 경우, 데이터 페이지를 다시 정렬해야 하므로 index rebalancing 을 해야 되어 성능상이 이슈가 발생할 수 있다. 반면 time-based ordered uuid 는 데이터를 추가하더라도 마지막 데이터 페이지에 추가되므로 index rebalancing 이 되지 않아 성능상 이점이 있다.

 

25000 개의 데이터를 삽입 때의 벤치마킹 데이터

 

3. JPA 에 UUID version7 을 적용.

implementation 'com.fasterxml.uuid:java-uuid-generator:4.3.0'
// UUID Version 7: 01870603-f211-7b9a-a7ea-4a98f5320ff8
System.out.println("UUID Version 7: " + UuidCreator.getTimeOrderedEpoch());

 

[1] Spring Boot 2.x

import java.io.Serializable;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

import com.github.f4b6a3.uuid.UuidCreator;

public class UuidV7Generator implements IdentifierGenerator {

	@Override
	public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o)
		throws HibernateException {
		return UuidCreator.getTimeOrderedEpoch();
	}

}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

	@Id
	@GeneratedValue(generator = "uuidV7")
	@GenericGenerator(name = "uuidV7",
		strategy = "com.cooper.common.data.generator.UuidV7Generator")
	@Column(columnDefinition = "BINARY(16)")
	private UUID id;
    
    ...
}

 

[2] Spring Boot 3.x

import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;

import java.util.EnumSet;

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;

import com.github.f4b6a3.uuid.UuidCreator;

public class UnixEpochTimeOrderedIdGenerator implements BeforeExecutionGenerator {
	@Override
	public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue,
		EventType eventType) {
		return UuidCreator.getTimeOrderedEpoch();
	}

	@Override
	public EnumSet<EventType> getEventTypes() {
		return INSERT_ONLY;
	}
}
@IdGeneratorType(UnixEpochTimeOrderedIdGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE } )
@Inherited
public @interface UnixTimeOrderedUuidGeneratedValue {
}
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {

	@Id
	@UnixTimeOrderedUuidGeneratedValue
	@Column(columnDefinition = "BINARY(16)")
	@Getter
	private UUID id;
...
}

 

Reference