15년간 UUID v4는 유일한 답이었다. 랜덤 128비트 식별자, 브라우저에서 생성, 데이터베이스 컬럼에 복붙, 어디에나 사용. 그것은 작동했다. 그런 다음 2024년에 RFC 9562가 v7을 표준화했고, 대화가 바뀌었다.
간단히 말해서: 새 데이터베이스 기본 키에는 v7을, 보안 토큰과 세션 ID에는 v4를 사용하라. 이유는 다음과 같다.
UUID의 실제 모습
UUID는 128비트 식별자이고, 일반적으로 대시로 구분된 32개의 16진수 문자열로 쓰인다: 550e8400-e29b-41d4-a716-446655440000. 대시는 장식이고, 바이트가 중요하다. 128비트는 2¹²⁸개의 가능한 값을 준다 — 약 340デシillion. 소진될 일은 없다.
버전 번호(두 번째 대시 후 첫 번째 숫자 — 550e8400-e29b-**4**1d4-a716-446655440000에서 4)는 UUID가 어떻게 생성되었는지 알려준다. 그 부분이 중요하다.
UUID v4: 순수 랜덤
UUID v4는 대부분의 사람들이 “UUID”라고 말할 때 의미하는 것이다. 128비트 중 122비트가 랜덤이고, 나머지 6비트는 버전과 변형을 인코딩한다. 랜덤성이 요점이다 — 활용할 패턴이 없고, 다음 ID를 추측할 방법이 없고, 시간 상관관계가 없다.
최신 브라우저에서 생성:
crypto.randomUUID() // → "550e8400-e29b-41d4-a716-446655440000"
아름다울 정도로 단순하다. 하지만 실제로는 데이터베이스에 끔찍하다.
문제는 B-tree 색인이다. B-tree는 대부분의 색인(Postgres, MySQL, SQLite, MongoDB) 뒤에 있는 데이터 구조이다. 데이터를 정렬하여 범위 쿼리가 빠르고 삽입이 디스크에서 대략 같은 위치로 가게 한다. 랜덤 UUID를 삽입하면 데이터베이스가 트리 중간 어딘가에 넣어야 한다 — 모든 삽입이 랜덤 I/O가 된다. 수백만 행이 있으면, 이것은 쓰기 처리량을 죽인다.
역사적 수정 방법은 “UUID v4를 사용하되 순차 정렬”이었다 — 하지만 v4는 정의상 랜덤이다. 사람들은 우회했다(MySQL의 UUID_TO_BIN(..., 1)이 시간 비트를 앞으로 교환). RFC 9562가 그 우회를 표준으로 만들었다.
UUID v7: 시간 정렬
UUID v7은 상위 비트에 밀리초 정밀도 타임스탬프를, 하위 비트에 랜덤성을 넣는다:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms | ver | rand_a |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|var| rand_b |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| rand_b |
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
처음 48비트는 밀리초 Unix 타임스탬프이다. 다음 4비트는 버전(7)이다. 그런 다음 74비트의 랜덤 비트, 2비트의 변형 비트, 48비트의 랜덤 비트가 더 있다.
결과: 같은 밀리초에 생성된 UUID는 삽입 순서로 정렬된다. 나중에 생성된 UUID는 나중에 정렬된다. 삽입은 B-tree의 오른쪽으로 간다, 중간이 아니라. 쓰기 처리량은 데이터베이스에 따라 2-10배 증가한다.
중요한 벤치마크
PlanetScale은 2024년에 숫자를 제시한 벤치마크를 발표했다. MySQL 8의 기본 키 색인에서:
| 형식 | 삽입/초 | 비고 |
|---|---|---|
| 자동 증가 INT | 35,000 | 가장 빠르지만 정보 유출 |
| UUID v7 | 18,000 | 대부분의 앱에서 INT와 비슷 |
| UUID v4 | 4,500 | 랜덤 삽입 = 페이지 분할 |
| UUID v1 (타임스탬포 포함) | 14,000 | 이전 시간 정렬 변형 |
v4와 v7 사이의 4배 차이는 랜덤 I/O를 하는 비용이다. 고트래픽 서비스의 경우, 이것은 곧바로 비용으로 전환된다.
v4를 사용해야 할 때
UUID v4는 ID 자체가 보안 경계일 때 여전히 올바른 선택이다:
- 세션 토큰 — 세션 ID가 생성 시간을 유출하게 하기 원하지 않는다. UUID v7은 시간으로 정렬되고, 이는 하나의 ID를 아는 공격자가 인접 ID를 추측할 수 있음을 의미한다. (실용적 악용 가능성은 낮지만 — 74비트의 랜덤성은 여전히 많다 — 하지만 원칙은 중요하다.)
- 비밀번호 재설정 토큰 — 같은 이유.
- 서드파티에 배포되는 API 키 — ID가 추측 불가능하도록 설계된 경우, 시간 정렬은 원하지 않는 기능이다.
- CSPRNG 출력으로 사용되는 모든 것 — v4는
crypto.randomUUID()가 반환하는 것이다. ID를 암호화 랜덤성으로 사용하는 경우, v4가 자연스러운 선택이다.
v7을 사용해야 할 때
UUID v7은 다음에 대한 올바른 기본값이다:
- 데이터베이스 기본 키 — 이것이 핵심 사용 사례이다. 대부분의 새 스키마는 v7을 선택해야 한다.
- 이벤트 로그 ID — 별도의 타임스탬프 컬럼 없이 시간으로 정렬 가능.
- 메시지 큐 메시지 ID — 고유성을 가진 FIFO 순서.
- 분산 시스템 식별자 — 시퀀스를 조정하지 않고도粗い 순서를 원하는 경우.
생성하는 방법
브라우저에서 v4는 내장되어 있다:
crypto.randomUUID() // v4
v7의 경우, 작은 폴리필이 필요하다 — crypto.getRandomValues에 타임스탬프 접두사를 더한 것. DevSpeedTools의 UUID 생성기는 crypto.getRandomValues를 직접 사용하여 브라우저에서 v4와 v7을 모두 생성하므로, 아무것도 설치하지 않고 ID를 스키마에 바로 붙여넣을 수 있다.
Node.js 22+에서, 내장된 crypto.randomUUID()는 여전히 v4만 반환한다. npm uuid 패키지(v9+)는 v7()을 추가했다. Python에서 uuid.uuid7()은 3.14(2025년 6월 출시)에 도착했고 이제 표준 라이브러리 문서에서 권장되는 선택이다. Postgres에서는 gen_random_uuid() 함수가 여전히 v4를 반환한다 — v7을 얻으려면 클라이언트 사이드에서 생성하여 전달한다. MySQL 9(2024년 출시)는 UUID_V7_BIN()을 1등급 타입으로 추가했고, MariaDB는 11.7에서 따랐다. Cloudflare D1, PlanetScale, Supabase는 모두 2026년에 새 기본 키 컬럼에 기본적으로 v7을 생성한다.
v4에서 v7로 마이그레이션
v4 기본 키가 있는 기존 테이블이 있는 경우, 마이그레이션할 필요가 없다. 두 버전은 같은 컬럼에서 공존한다(uuid 타입은 둘 다 허용). 성능 향상은 삽입에만 해당되고, 새 행을 추가하는 경우에만 중요하다. 가장 간단한 마이그레이션: 새 삽입에 v7을 생성하기 시작하고, 기존 행은 v4로 둔다. 새 v7 행과 약간 순서가 어긋나지만(v4에는 타임스탬프가 없으므로), 색인은 괜찮다.
2026년 새 스키마의 경우, v7이 기본값이다. 4배의 쓰기 처리량 이득은 실제이고, 사양은 안정적이며, 모든 주요 언어 런타임에 v7 생성기가 있고, 관리형 데이터베이스 제공자가 새 기본 키에 기본적으로 v7을 사용하고 있다. 추측 불가능하고 시간 상관관계가 없는 ID가 필요한 경우에만 v4를 사용하라.