UUID v4와 v7, 기본키로는 무엇을 쓸까
v4는 무작위라 인덱스에 부담을 주고, v7은 시간순으로 정렬되는 대신 생성 시각이 드러납니다. 버전별 차이와 선택 기준을 정리했습니다.
식별자를 자동 증가 정수 대신 UUID로 두는 이유는 대체로 비슷합니다. 여러 서버나 클라이언트에서 미리 만들어도 겹치지 않고, 주소에 노출해도 다른 레코드의 존재나 개수가 드러나지 않기 때문입니다.
문제는 어느 버전을 쓰느냐입니다. 관행적으로 v4를 쓰지만, 기본키로 놓으면 성능에서 대가가 있습니다.
v4는 무작위다
v4는 122비트가 난수입니다. 사실상 겹치지 않고, 값에서 아무 정보도 읽어 낼 수 없습니다. 이 두 가지가 v4의 장점입니다.
정렬 순서가 없다는 점이 대가입니다. 데이터베이스 기본키는 대개 B-tree 인덱스이고, 자동 증가 정수는 항상 마지막 페이지 뒤에 붙습니다. 최근에 쓰는 페이지가 좁아 캐시에 잘 남습니다.
v4는 삽입 위치가 매번 인덱스의 임의 지점입니다.
- 넣을 때마다 다른 페이지를 읽어야 합니다
- 페이지가 차면 분할이 일어나 빈 공간이 늘어납니다
- 인덱스가 메모리보다 커지는 순간부터 디스크 읽기가 늘어납니다
MySQL InnoDB는 기본키가 곧 데이터 저장 순서(클러스터드 인덱스)라 영향이 더 큽니다. PostgreSQL은 힙에 따로 저장하므로 덜하지만, 인덱스 자체의 분할은 같습니다.
행이 수십만 개 수준이면 차이를 느끼기 어렵습니다. 수천만 행에 삽입이 잦은 테이블에서 드러납니다.
v7은 앞부분이 시간이다
v7은 앞 48비트가 밀리초 단위 유닉스 타임스탬프이고, 나머지가 난수입니다. 결과적으로 생성 순서대로 정렬됩니다.
- 삽입이 인덱스 끝쪽에 모입니다. 정수 키와 비슷한 성질입니다
- 정렬하면 시간순이 되므로
created_at으로 정렬하던 곳을 대신할 수 있습니다 - 같은 밀리초에 여러 개를 만들어도 뒤쪽 난수로 구분됩니다
대가는 생성 시각이 값에 드러난다는 점입니다. 공개 URL에 v7을 쓰면 그 리소스가 언제 만들어졌는지 밀리초까지 알 수 있습니다. 주문 번호나 가입 순서를 감춰야 한다면 고려할 부분입니다. 두 값을 비교하면 어느 쪽이 먼저인지도 알 수 있습니다.
v7은 2024년 RFC 9562로 표준이 되었습니다. 언어별 표준 라이브러리 지원은 아직 고르지 않아, 환경에 따라 별도 패키지가 필요할 수 있습니다.
나머지 버전
v1 — 시간과 MAC 주소를 씁니다. 기기를 식별할 수 있어 지금은 권장되지 않습니다. 시간 기반이지만 타임스탬프 조각이 뒤섞여 저장되므로 문자열로 정렬해도 시간순이 되지 않습니다.
v6 — v1의 필드 순서를 바꿔 정렬이 되게 고친 것입니다. 기존 v1 데이터가 있는 시스템을 위한 선택지입니다. 새로 만든다면 v7 쪽입니다.
v5 — 네임스페이스와 이름을 해시해 만듭니다. 같은 입력에서 항상 같은 UUID가 나옵니다. 외부 시스템의 키를 내부 UUID로 옮길 때처럼 결정적인 식별자가 필요한 경우에 씁니다. 난수가 아니므로 추측할 수 없는 값이 필요한 곳에는 쓰지 않습니다.
ULID — 표준은 아니지만 자주 쓰입니다. 26자 Base32라 UUID의 36자보다 짧고, 시간순으로 정렬되며, 사전순 정렬과 시간순이 일치합니다. v7이 표준이 된 지금은 v7 쪽 호환성이 낫습니다.
어떻게 저장하나
버전과 별개로 저장 형식이 성능에 영향을 줍니다.
UUID는 128비트이므로 16바이트면 충분합니다. 하이픈을 포함한 문자열로 저장하면 36바이트이고, 기본키는 모든 보조 인덱스에 복사되므로 이 차이가 여러 번 반복됩니다.
- PostgreSQL —
uuid타입을 씁니다. 내부적으로 16바이트입니다 - MySQL —
BINARY(16)에 넣습니다. 조회할 때만 문자열로 바꿉니다
정리
- 공개 주소에 노출되고 생성 시각을 숨겨야 한다 → v4
- 대량 삽입이 있는 내부 테이블의 기본키 → v7
- 같은 입력에서 같은 ID가 나와야 한다 → v5
- 세션 토큰, 비밀번호 재설정 링크 → UUID 대신 암호학적 난수를 씁니다. UUID는 충돌을 피하려고 만든 것이지 비밀을 만들려고 만든 것이 아닙니다
마지막 항목이 실무에서 자주 어긋납니다. v4는 예측하기 어렵지만, 난수원의 품질을 보장하지 않는 구현도 있습니다. 비밀값이 필요하면 그 용도의 함수를 씁니다.
tools.onuel.dev에서 v4와 v7을 여러 개 만들어 정렬 차이를 눈으로 볼 수 있습니다. v5와 ULID도 같이 있습니다.
- #UUID
- #데이터베이스
- #식별자