UUID 완벽 가이드: 128비트 구조, 버전별 원리, 실전 활용 사례
서비스에 가입할 때마다 계정을 위한 고유 식별자가 생성됩니다. 모든 API 요청에는 추적 ID가 실립니다. 분산 데이터베이스의 모든 행에는 다른 머신에서 생성된 키와 충돌하지 않는 기본 키가 필요합니다. 이 모든 것의 뒤에 있는 해답은 바로 UUID — Universally Unique Identifier(범용 고유 식별자) 입니다.
이 가이드에서는 UUID가 무엇인지, 어떤 구조로 되어 있는지, 각 버전이 내부적으로 어떻게 동작하는지, 그리고 언제 사용해야 하는지(또는 피해야 하는지)를 설명합니다.
UUID 한눈에 보기
UUID는 중앙 기관 없이 전역적으로 고유하도록 설계된 128비트(16바이트) 식별자입니다. 표준 8-4-4-4-12 포맷으로 32자리 16진수를 사용해 표기합니다.
550e8400-e29b-41d4-a716-446655440000
|------| |--| |--| |--| |----------|
8 hex 4 4 4 12 hex
16진수 32자 + 하이픈 4개 = 총 36자입니다. 하이픈은 순전히 가독성을 위한 것이며, 데이터를 담고 있지 않습니다.
핵심 요약:
- 128비트 = 2¹²⁸ ≈ 3.4 × 10³⁸ 가지 값
- RFC 9562(2024년 5월, RFC 4122 대체)에 의해 표준화
- Microsoft 생태계에서는 GUID(Globally Unique Identifier)라고도 부릅니다. 포맷은 동일하며 이름만 다릅니다
- PostgreSQL(
uuid타입), MySQL(BINARY(16)또는CHAR(36)), 그리고 거의 모든 프로그래밍 언어가 기본 지원합니다
UUID 내부 구조
모든 UUID는 버전과 무관하게 고정된 비트 위치에 두 개의 메타데이터 필드를 인코딩합니다.
550e8400-e29b-41d4-a716-446655440000
^ ^
| |
버전 ─┘ └─ 변형(Variant)
버전 필드(48~51비트)
13번째 16진수 자리(세 번째 그룹의 첫 자리)가 UUID 버전을 나타냅니다.
| 16진수 자리 | 버전 | 방식 |
|---|---|---|
1 | v1 | 타임스탬프 + MAC 주소 |
3 | v3 | 네임스페이스 + 이름의 MD5 해시 |
4 | v4 | 암호학적 난수 |
5 | v5 | 네임스페이스 + 이름의 SHA-1 해시 |
6 | v6 | 재정렬된 타임스탬프(RFC 9562) |
7 | v7 | Unix 타임스탬프 + 난수(RFC 9562) |
8 | v8 | 사용자 정의/구현별 고유 |
변형(Variant) 필드(64~65비트)
17번째 16진수 자리(네 번째 그룹의 첫 자리)가 변형을 나타냅니다. RFC 4122/9562 UUID의 경우 앞 비트가 10이므로, 이 자리는 항상 8, 9, a, b 중 하나입니다.
예시 분석
550e8400-e29b-41d4-a716-446655440000
↑ ↑
4 → v4 a → RFC 4122 variant
이것은 UUID v4(난수), RFC 4122/9562 variant입니다.
UUID 버전별 상세 설명
버전 1: 타임스탬프 + MAC 주소
UUID v1은 초기 설계 방식입니다. 다음 정보를 인코딩합니다.
- 60비트 타임스탬프 — 1582년 10월 15일(그레고리력 개혁일)부터 100나노초 단위 간격
- 14비트 클럭 시퀀스 — 시계 되돌림 시 중복을 막기 위한 단조 증가 카운터
- 48비트 노드 — 일반적으로 머신의 MAC 주소
| Timestamp | Ver | Clk |Var| Node (MAC) |
| 60 bits | 4b | 14b |2b | 48 bits |
문제점:
- 생성 시각과 하드웨어 신원이 노출됨(프라이버시 위험)
- MAC 주소는 위조할 수 있어 고유성을 약화시킴
- 1582년 에폭이 혼란스럽고 변환이 필요함
결론: RFC 9562에서 deprecated 처리. 시간 기반 UUID가 필요하면 v7을 사용하세요.
버전 3: MD5 이름 기반(결정론적)
UUID v3은 네임스페이스 UUID 와 이름 문자열 을 MD5로 해싱합니다. 동일한 입력은 항상 동일한 UUID를 생성합니다.
import uuid
# namespace = DNS, name = "example.com"
print(uuid.uuid3(uuid.NAMESPACE_DNS, "example.com"))
# → "9073926b-929f-31c2-abc9-fad77ae3e8eb" (항상 이 값)
네 가지 표준 네임스페이스가 정의되어 있습니다.
- DNS:
6ba7b810-9dad-11d1-80b4-00c04fd430c8 - URL:
6ba7b811-9dad-11d1-80b4-00c04fd430c8 - OID:
6ba7b812-9dad-11d1-80b4-00c04fd430c8 - X.500:
6ba7b814-9dad-11d1-80b4-00c04fd430c8
결론: 동작은 하지만 v5를 권장합니다. SHA-1이 MD5보다 강력합니다.
버전 4: 난수 기반 — 가장 인기 있는 버전
UUID v4는 122비트 를 암호학적으로 안전한 난수로 채웁니다(나머지 6비트는 버전/변형 필드용으로 예약됨).
| Random | Ver | Random |Var| Random |
| 48 bits | 4b | 12 bits |2b | 62 bits |
2¹²² ≈ 5.3 × 10³⁶ 가지 값이 가능하므로, 충돌 확률은 천문학적으로 낮습니다. 충돌이 한 번이라도 발생할 확률이 50%에 도달하려면 약 2.71 × 10¹⁸개의 UUID가 필요합니다. 이는 271경 개에 해당합니다.
// 모든 최신 브라우저와 Node.js에서 기본 지원
const id = crypto.randomUUID();
console.log(id); // → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
장점: 단순함, 프라이버시 보호, 범용적 지원, 조정 불필요.
단점: 난수 분포로 인해 데이터베이스 기본 키로 사용하면 B-tree 인덱스가 파편화됩니다. 데이터베이스 중심 사용 사례에서는 v7을 고려하세요.
버전 5: SHA-1 이름 기반(결정론적)
v3와 동일하지만 MD5 대신 SHA-1을 사용합니다. 동일한 입력은 항상 동일한 UUID를 생성합니다.
import uuid
print(uuid.uuid5(uuid.NAMESPACE_DNS, "example.com"))
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17" (항상 이 값)
활용 사례:
- URL 또는 DNS 이름으로부터 안정적인 ID 생성
- 콘텐츠 주소 지정 방식 저장소의 키
- 재현 가능한 테스트 픽스처
중요: v3과 v5는 보안 목적이 아닙니다. 결정론적이므로, 네임스페이스와 이름을 아는 사람은 누구나 같은 UUID를 재현할 수 있습니다.
버전 7: Unix 타임스탬프 + 난수(신규 프로젝트 권장)
UUID v7은 가장 최신 버전으로, RFC 9562(2024년 5월) 에서 도입되었습니다. 다음을 인코딩합니다.
- 48비트 Unix 타임스탬프(밀리초 단위) — 단조 증가
- 74비트 의 암호학적 난수
| Unix timestamp (ms) | Ver | rand_a |Var| rand_b |
| 48 bits | 4b | 12 bits |2b | 62 bits |
이는 곧 v7 UUID가 생성 시각 순으로 자연스럽게 정렬된다는 뜻입니다. 즉, 새로운 UUID가 항상 이전 UUID보다 사전순으로 큽니다. 이 특성 덕분에 B-tree 인덱스가 무작위로 파편화되는 대신 순차 구조를 유지할 수 있으므로 데이터베이스 기본 키로 이상적입니다.
import { v7 as uuidv7 } from "uuid";
const id1 = uuidv7(); // T₁ 시점에 생성
const id2 = uuidv7(); // T₂ 시점에 생성 (T₂ > T₁)
console.log(id1 < id2); // → true (사전순 비교)
데이터베이스에서 왜 중요한가: v7의 순차 특성은 v4에 비해 인덱스 페이지 분할을 최대 90%까지 줄여, 더 빠른 삽입, 더 작은 인덱스, 더 나은 캐시 성능을 제공합니다.
UUID와 GUID — 차이점은?
기능적 차이는 없습니다. GUID(Globally Unique Identifier)는 Microsoft가 UUID를 부르는 이름으로, Windows, .NET, COM, SQL Server에서 사용됩니다. 포맷은 동일합니다. 128비트, 8-4-4-4-12 16진수.
유일한 표면적 차이는 Microsoft 도구가 가끔 GUID를 대문자와 중괄호 로 표시한다는 점입니다.
UUID: 550e8400-e29b-41d4-a716-446655440000
GUID: {550E8400-E29B-41D4-A716-446655440000}
“UUID와 GUID의 차이”를 묻는다면, 답은 브랜딩 차이일 뿐입니다.
특수 UUID 값
RFC 9562는 두 개의 특수 UUID를 정의합니다.
| 이름 | 값 | 용도 |
|---|---|---|
| NIL UUID (NIL 형식) | 00000000-0000-0000-0000-000000000000 | 값의 부재를 나타냄(예: null) |
| MAX UUID (MAX 형식) | ffffffff-ffff-ffff-ffff-ffffffffffff | 경계 표식 또는 센티넬 값 |
이 값들은 정의상 고유하지 않으므로 실제 식별자로 절대 사용하지 마십시오.
충돌 확률: 생일 문제
“생일 문제”는 충돌이 일어날 가능성이 유의미해질 때까지 얼마나 많은 UUID가 필요한지를 계산합니다. UUID v4(122비트 난수)의 경우를 보겠습니다.
| 생성된 UUID 수 | 충돌 확률 |
|---|---|
| 100만 개 | ~10⁻²² (사실상 불가능) |
| 10억 개 | ~10⁻¹⁶ (여전히 무시할 수 있음) |
| 2.71 × 10¹⁸개 | 50% (“생일 한계”) |
맥락을 제공하자면, 초당 10억 개의 UUID를 생성한다고 가정해도 단 한 번의 충돌이 발생할 확률이 50%에 이르기까지 86년이 걸립니다. 실제로는 하드웨어 고장, 소프트웨어 버그, 우주 방사선(cosmic ray) 모두가 UUID v4 수학보다 중복을 일으킬 가능성이 더 높습니다.
공식: p(n) ≈ n² / (2 × 2¹²²)
UUID 유효성 검사 방법
유효한 UUID는 다음 정규식 패턴과 일치합니다(대소문자 구분 없음).
^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
이 패턴은 다음을 확인합니다.
- 8-4-4-4-12 16진수 포맷
- 버전 자리가 1~7(15번째 위치)
- 변형 니블이 8, 9, a, b로 시작(20번째 위치)
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
}
isValidUUID("550e8400-e29b-41d4-a716-446655440000"); // → true
isValidUUID("not-a-uuid"); // → false
언어별 UUID 생성 방법
JavaScript / TypeScript
// 브라우저와 Node.js 기본 지원 — v4
crypto.randomUUID();
// npm uuid 패키지 — v1, v3, v4, v5, v7 지원
import { v4, v7 } from "uuid";
v4(); // 난수
v7(); // 시간순 정렬
Python
import uuid
uuid.uuid4() # 난수
uuid.uuid5(uuid.NAMESPACE_DNS, "example.com") # 결정론적
# uuid.uuid7()은 Python 3.14+에서 제공 예정
Go
import "github.com/google/uuid"
uuid.New() // v4 난수
uuid.Must(uuid.NewV7()) // v7 시간순 정렬
Java
import java.util.UUID;
UUID.randomUUID(); // v4 난수
// UUID v7: com.fasterxml.uuid 사용 또는 JDK 21+의 java.util.UUID
SQL (PostgreSQL)
-- v4 (PostgreSQL 13+)
SELECT gen_random_uuid();
-- v7 (PostgreSQL 18+)
SELECT uuidv7();
자주 쓰이는 활용 사례
데이터베이스 기본 키
UUID를 사용하면 애플리케이션, 클라이언트, 엣지 어디에서든 데이터베이스 왕복 없이 ID를 생성할 수 있습니다. 이는 오프라인 우선 아키텍처를 가능하게 하고 분산 시스템을 단순화합니다. 최상의 인덱스 성능이 필요하면 v7을, 순서가 중요하지 않다면 v4를 사용하세요.
API 요청 추적
진입점(게이트웨이, 로드 밸런서)에서 모든 API 요청에 UUID를 할당합니다. X-Request-ID 같은 헤더로 하위 모든 서비스에 이 값을 전달합니다. 이렇게 하면 마이크로서비스 전반의 로그를 상호 연관시키는 일이 아주 쉬워집니다.
멱등성(Idempotency) 키
API는 재시도된 요청이 중복 리소스를 만들지 않도록 UUID를 멱등성 키로 사용합니다. 클라이언트는 첫 시도 전에 UUID를 생성하고, 재시도 시에도 동일한 UUID를 보냅니다.
세션 식별자
UUID는 대규모 사용자 기반에서도 세션 충돌을 방지하기에 충분한 고유성을 제공합니다. 자동 증가 정수와 달리 열거될 수 없으므로, 공격자가 숫자를 증가시키며 유효한 세션 ID를 추측할 수 없습니다.
콘텐츠 주소 지정 방식 저장소
UUID v5는 콘텐츠로부터 결정론적 ID를 생성합니다. 같은 입력이면 항상 같은 UUID를 얻을 수 있어, 중복 제거, 캐싱, 재현 가능한 빌드에 유용합니다.
보안 고려 사항
UUID는 보안 토큰이 아님
UUID는 고유성을 위해 설계된 것이지 기밀성을 위한 것이 아닙니다. 주요 문제는 다음과 같습니다.
- UUID v1 은 생성 타임스탬프와 MAC 주소를 노출합니다
- UUID v4 는 122비트의 난수를 가지지만 구조가 예측 가능합니다(버전/변형 비트가 고정됨)
- UUID v3/v5 는 결정론적입니다. 네임스페이스와 이름을 아는 사람은 누구나 UUID를 재현할 수 있습니다
보안 토큰, API 키, 세션 시크릿에는 128비트 이상의 순수한 난수를 생성하는 전용 CSPRNG를 사용하세요.
// 보안 토큰용 — UUID가 아니라 완전한 난수
const token = Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map(b => b.toString(16).padStart(2, "0"))
.join("");
UUID v7의 생성 시각 노출 문제
UUID v7의 앞 48비트는 생성 타임스탬프를 밀리초 단위로 인코딩합니다. v7 UUID를 수신한 쪽은 언제 생성되었는지 추출할 수 있습니다.
const hex = "01906b5e-4a3e-7234-8f56-b8c12d4e5678".replace(/-/g, "").slice(0, 12);
new Date(parseInt(hex, 16));
// → 2024-07-01T12:34:56.000Z
생성 시각이 민감한 정보라면 v4를 대신 사용하세요.
열거 방지 수단으로서의 한계
UUID는 순차 정수보다 추측하기 어렵지만, 유일한 접근 제어 수단이 되어서는 안 됩니다. 반드시 권한 검사를 수행하세요. URL의 모호함에만 의존하지 마십시오.
자주 묻는 질문
UUID에는 왜 하이픈이 있나요?
8-4-4-4-12 포맷의 하이픈은 순전히 사람이 읽기 편하도록 존재합니다. 데이터를 담고 있지 않으며 파싱 시 무시됩니다. 일부 시스템은 UUID를 하이픈 없이 저장하기도 합니다(16진수 32자). 이것도 마찬가지로 유효합니다.
두 UUID가 같을 수 있나요?
이론적으로는 가능하지만 실제로는 그렇지 않습니다. 122비트 난수를 사용하는 UUID v4의 경우, 임의의 두 UUID가 동일하게 생성될 확률은 약 5.3 × 10³⁶ 분의 1입니다. 실제 생성 속도에서는 복권에 당첨되면서 번개에 맞을 확률이 UUID 충돌을 마주할 확률보다 높습니다.
UUID는 순차적인가요?
일부 버전만 그렇습니다. UUID v1, v6, v7은 타임스탬프를 포함하여 시간순으로 정렬됩니다. UUID v4는 완전한 난수이며 순서가 없습니다. UUID v3과 v5는 결정론적이지만 순서는 없습니다.
UUID는 저장 공간을 얼마나 차지하나요?
- 바이너리: 16바이트(128비트) — 가장 효율적인 저장 방식
- 문자열(하이픈 포함): 36바이트(ASCII)
- 문자열(하이픈 제외): 32바이트(ASCII)
대부분의 데이터베이스는 내부적으로 UUID를 바이너리 포맷으로 저장합니다. PostgreSQL의 기본 uuid 타입은 정확히 16바이트를 사용합니다.
기본 키로 UUID와 자동 증가 중 어느 것을 사용해야 하나요?
자동 증가는 단일 데이터베이스 애플리케이션에 더 단순합니다(작고, 빠르고, 순차적). UUID는 분산 시스템에 더 적합합니다(어디서든 생성, 조정 불필요, 머지에 안전). UUID를 사용한다면 최상의 데이터베이스 성능을 위해 v7을 선호하세요.
RFC 9562란 무엇인가요?
RFC 9562는 2024년 5월에 발행된 최신 UUID 표준입니다. RFC 4122를 대체하며, UUID 버전 6, 7, 8을 공식적으로 도입합니다. v1을 v6/v7로 대체하는 방향으로 deprecated 처리하고, NIL UUID와 MAX UUID 값을 정의합니다. UUID 생성 또는 유효성 검사를 구현한다면 RFC 9562가 권위 있는 참조 문서입니다.
여러 프로그래밍 언어에서 UUID를 함께 사용할 수 있나요?
네. UUID 포맷(128비트, 8-4-4-4-12 16진수)은 언어 중립적입니다. JavaScript에서 생성한 UUID는 Python, Go, Java 또는 UUID를 지원하는 모든 언어에서 올바르게 파싱됩니다. 이러한 상호 운용성은 UUID의 큰 강점 중 하나입니다.
우리의 UUID Generator로 UUID를 즉시 생성, 디코딩, 유효성 검사하세요. v1, v4, v5, v7을 지원하고 배치 생성이 가능하며, 100% 브라우저 안에서 동작합니다.
다음 프로젝트에 UUID 버전을 고르고 계신가요? UUID v4, v7, ULID, Snowflake 비교 가이드에서 데이터베이스 벤치마크와 코드 예제를 포함한 실전 선택 가이드를 곧 제공할 예정입니다.