프로젝트에서 식별자를 설계할 때 Long auto increment를 쓸지, UUID를 쓸지 고민한 적이 있다. 외부에 노출되는 ID를 예측하기 어렵게 만들고 싶을 때 UUID는 괜찮은 선택지로 보였다.
하지만 UUID를 PK로 쓰면 인덱스 성능에 영향을 줄 수 있다는 이야기도 자주 보였다. 처음에는 UUID가 예측하기 어렵고 분산 환경에도 좋아 보여서 바로 써도 괜찮겠다고 생각했는데, 인덱스에 값이 들어가는 방식을 확인하면서 무작정 고를 문제는 아니라고 느꼈다. 그래서 UUID 종류별로 어떤 특징이 있는지 먼저 확인했다.
이 글은 UUIDv4, ULID, UUIDv7을 비교하면서 인덱스 관점에서 어떤 차이가 있는지 정리한 기록이다.
문제 상황
UUIDv4는 랜덤 값에 가깝다. 예측하기 어렵다는 장점이 있지만, B-Tree 인덱스에서는 새 값이 들어가는 위치가 계속 흩어질 수 있다.
auto increment ID는 값이 증가하므로 보통 인덱스 뒤쪽에 순서대로 들어간다. 반면 UUIDv4는 새 값이 인덱스의 여러 위치에 들어갈 수 있다. 이 때문에 페이지 분할이 늘거나 캐시 효율이 떨어질 수 있다.
즉, UUIDv4가 항상 느리다는 뜻보다는, 쓰기 패턴과 인덱스 구조에 따라 불리할 수 있다고 이해했다.
해결 방법
UUIDv4의 랜덤성이 부담된다면 시간 순서가 어느 정도 반영되는 식별자를 고려할 수 있다. 이때 ULID와 UUIDv7이 후보가 된다.
ULID는 시간 정보와 랜덤 정보를 함께 사용한다. 문자열로 표현했을 때 정렬하기 쉽다는 장점도 있다. UUIDv7도 시간 기반 UUID라서 기존 UUID 형태를 유지하면서 정렬에 더 유리한 특성을 가진다.
두 방식이 완전히 같은 선택지는 아니지만, 둘 다 랜덤 UUID보다 인덱스에 값이 들어가는 위치를 조금 더 예측하기 쉽게 만들 수 있다.
트레이드오프
그렇다고 시간 순서가 들어간 식별자가 항상 더 좋은 것은 아니다. 랜덤성이 필요한 경우도 있다.
예를 들어 외부에 노출되는 ID에서 생성 시점을 추측하기 어렵게 만들고 싶거나, 특정 도메인에서 순서 정보가 드러나는 것이 부담이라면 랜덤 UUID가 더 적절할 수 있었다.
반대로 내부 PK나 쓰기 성능을 많이 보는 테이블에서는 정렬 가능한 식별자가 더 나을 수 있다.
결국 식별자는 성능, 노출되는 정보, 구현 편의성을 함께 보고 선택해야 했다.
선택할 때 본 것
프로젝트에서 식별자를 고를 때는 아래 내용을 확인한다.
- 외부에 노출되는 ID인가
- 생성 순서가 드러나도 괜찮은가
- 쓰기 성능과 인덱스 효율을 많이 보는 테이블인가
- DB와 애플리케이션에서 해당 타입을 다루기 쉬운가
- 분산 환경에서 ID를 어떻게 생성할 것인가
이런 조건 없이 단순히 UUID가 좋다거나 나쁘다고 말하기는 어려웠다.
정리
UUID PK와 인덱스 성능을 보면서 확인한 것은, 식별자 선택도 도메인과 사용 조건에 따라 달라진다는 점이었다.
UUIDv4는 예측하기 어렵다는 장점이 있지만 인덱스 쓰기 패턴에서는 불리할 수 있었다. ULID와 UUIDv7은 시간 순서 특성으로 이 문제를 줄일 수 있지만, 순서 정보가 어느 정도 드러날 수 있다.
식별자는 단순한 값이 아니라 데이터가 쌓이고 조회되는 방식에도 영향을 주는 설계 요소였다.