이전 글에서는 API 테스트 과정에서 UUID만으로는 도메인 데이터를 식별하기 어렵다는 문제를 해결하기 위해 도메인 prefix + UUID 형태의 code를 생성하도록 개선한 과정을 정리했다.
이번 글에서는 그 이후 code에 사용하던 UUID를 v4에서 v7로 변경한 이유를 정리해보려고 한다.
기존 방식
처음에는 Java에서 기본으로 제공하는 UUID.randomUUID()를 사용했다.
private String createUuid() {
return UUID.randomUUID().toString();
}
이 방식은 간단하고 고유한 값을 생성하기에 충분했다.
생성되는 code는 다음과 같은 형태였다.
product-69a29997-2c97-433b-a3a5-1656721a4efc
하지만 PR 리뷰 과정에서 UUID v4보다 시간 순서 기반 UUID를 사용하는 것이 더 좋을 수 있다는 의견을 받았다.

UUID v4의 특징
UUID v4는 랜덤 기반 UUID다.
값이 무작위로 생성되기 때문에 고유성을 확보하기에는 좋지만, 생성 시점에 따른 순서를 파악하기는 어렵다.
예를 들어 데이터가 다음 순서로 생성되더라도,
1번째 생성
2번째 생성
3번째 생성
UUID 값만 보면 어떤 데이터가 먼저 생성되었는지 알기 어렵다.
7b421a11-8e45-4b62-a9d7-42143788122a
69a29997-2c97-433b-a3a5-1656721a4efc
9c71d442-0af3-4e52-b80c-3a521a21c11b
API 테스트나 로그 확인 과정에서는 단순히 고유한 값뿐 아니라, 어느 정도 생성 순서를 파악할 수 있는 식별자가 도움이 될 수 있다.
UUID v7을 고려한 이유
UUID v7은 시간 기반 정보를 포함하는 UUID다.
따라서 랜덤 기반 UUID v4보다 생성 순서에 가까운 정렬이 가능하다.
프로젝트에서는 code를 단순한 외부 식별자처럼 사용했지만, API 테스트나 DB 확인 과정에서는 생성된 순서를 함께 파악할 수 있으면 더 편리하다고 판단했다.
또한 랜덤 UUID를 인덱스로 사용할 경우 값이 무작위로 분포하기 때문에, 데이터베이스 인덱스 관점에서 불리할 수 있다는 의견도 있었다.
반면 시간 순서가 반영된 UUID를 사용하면 완전히 랜덤한 값보다 정렬과 인덱스 관리 측면에서 더 유리할 수 있다고 판단했다.
적용 방식
Java 기본 UUID.randomUUID() 대신 UUID 생성 라이브러리를 사용했다.
import com.github.f4b6a3.uuid.UuidCreator;
그리고 UUID 생성 로직을 다음과 같이 변경했다.
private String createUuid() {
return UuidCreator.getTimeOrderedEpoch().toString();
}
기존 코드가 다음과 같았다면,
this.code = getEntityPrefix() + "-" + UUID.randomUUID().toString();
변경 후에는 다음과 같이 수정했다.
String uuid = UuidCreator.getTimeOrderedEpoch().toString();
this.code = getEntityPrefix() + "-" + uuid;
최종적으로 생성되는 code는 기존과 동일하게 도메인 prefix + UUID 형태를 유지한다.
product-018f8c2b-7c4a-7d6e-9f2a-9b21f23a1c44
다만 UUID 부분이 랜덤 기반 v4에서 시간 순서 기반 UUID로 변경되었다.
@UuidGenerator를 사용하지 않은 이유
리뷰 과정에서 Hibernate의 @UuidGenerator를 사용하는 방법도 검토했다.
@Id
@UuidGenerator(style = UuidGenerator.Style.TIME)
private String id;
하지만 프로젝트에서 필요한 값은 단순 UUID가 아니라 도메인 prefix + UUID 형태의 code였다.
product-UUID
post-UUID
favorite-UUID
@UuidGenerator는 엔티티가 저장될 때 UUID 값을 자동 생성해주는 방식이다.
하지만 우리가 원하는 것처럼 UUID 앞에 도메인 prefix를 붙여 하나의 code로 구성하는 방식에는 적합하지 않다고 판단했다.
저장 이후에 prefix를 추가하는 방식도 생각해볼 수 있었지만, code는 저장되기 전에 완성되는 것이 더 자연스럽다고 생각했다.
그래서 별도의 UUID 생성 라이브러리를 사용하고, @PrePersist에서 직접 code를 구성하는 방식을 유지했다.
정리
처음에는 UUID.randomUUID()를 사용해 code를 생성했다. 이 방식은 간단하고 고유성을 확보하기에는 충분했다.
하지만 PR 리뷰 과정에서 시간 순서 기반 UUID를 사용하면 생성 순서를 파악하기 쉽고, 인덱스 관점에서도 더 나은 선택이 될 수 있다는 의견을 받았다.
그 결과 UUID v4 대신 UUID v7 기반 값을 생성하는 방식으로 변경했다.
이번 경험을 통해 식별자를 설계할 때는 단순히 고유성만 고려하는 것이 아니라, 다음과 같은 요소도 함께 고려해야 한다는 점을 알게 되었다.
- 사람이 값을 확인할 때 식별하기 쉬운가
- 생성 순서를 어느 정도 파악할 수 있는가
- 데이터베이스 인덱스 관점에서 불리하지 않은가
- 프로젝트에서 원하는 식별자 형태를 유연하게 만들 수 있는가
결국 식별자는 단순히 중복되지 않는 값이 아니라, 프로젝트의 사용 방식과 운영 편의성까지 함께 고려해서 설계해야 한다고 느꼈다.
'Back-End > Spring' 카테고리의 다른 글
| [JPA] @ManyToOne, @OneToMany 연관관계 정리 (0) | 2025.11.22 |
|---|---|
| [SpringBoot] Entity를 DTO로 변환해야 하는 이유 (0) | 2025.11.22 |
| UUID만으로 구분하기 어려운 데이터를 @PrePersist로 개선해보기 (0) | 2025.11.11 |
| [JWT] Bearer는 왜 붙을까? Basic 인증과 Bearer 인증 비교하기 (0) | 2025.10.29 |
| [SpringBoot] 스프링부트는 어떻게 응답(Response)할까요? (0) | 2025.10.20 |