UUID란 무엇인가?
UUID(Universally Unique Identifier, 범용 고유 식별자)는 마이크로소프트 용어로는 GUID(Globally Unique Identifier, 전역 고유 식별자)라고도 불리는 128비트 숫자로, 컴퓨터 시스템에서 정보를 고유하게 식별하는 데 사용됩니다. 다음 값을 부여하기 위해 중앙 권한이 필요한 자동 증가 정수와 달리, UUID는 어떤 컴퓨터에서도, 언제든지 독립적으로 생성할 수 있으며 충돌 확률이 천문학적으로 낮습니다.
UUID의 표준 텍스트 표현은 다음과 같습니다:
550e8400-e29b-41d4-a716-446655440000
^^^^^^^^ ^^^^ ^^^^ ^^^^ ^^^^^^^^^^^^
시간 시간 버전 변형 노드
이 하이픈이 포함된 문자열은 32개의 16진수 자리(128비트)를 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx 형식으로 인코딩합니다. 여기서:
- M은 버전 비트 (1, 3, 4, 5, 또는 7)
- N은 변형 비트 (RFC 4122 준수 UUID의 경우 8, 9, a, 또는 b)
간략한 역사
범용 고유 식별자의 개념은 1980년대 후반 Apollo Computer와 **Digital Equipment Corporation(DEC)**에서 **네트워크 컴퓨팅 시스템(NCS)**과 **분산 컴퓨팅 환경(DCE)**의 일부로 탄생했습니다. 목표는 중앙 레지스트리 없이도 분산 시스템이 식별자를 생성할 수 있도록 하는 것이었습니다.
마이크로소프트는 이 개념을 COM/OLE에 채택하여 GUID라고 불렀지만, 기본 구조는 UUID와 동일합니다. 두 용어는 실제로 같은 의미로 사용됩니다.
공식 사양은 RFC 4122로 2005년 7월 IETF에 의해 발행되었습니다. 5개의 UUID 버전(v1–v5)을 표준화하고 변형 비트를 정의했습니다. 20년 후, RFC 9562(2024년 5월 발행)가 RFC 4122를 대체하고 UUID v6과 v7을 공식적으로 추가하여 초기 버전의 데이터베이스 성능 문제를 해결했습니다.
UUID 구조: 128비트 설명
UUID는 항상 정확히 128비트(16바이트)입니다. 문자열로 서식화하면 36자(32개의 16진수 자리 + 4개의 하이픈)가 됩니다.
필드 비트수 16진수 자리 설명
────────────────────────────────────────────────────────
time_low 32 8 타임스탬프 하위 32비트 (v1)
time_mid 16 4 타임스탬프 중간 16비트 (v1)
time_hi_and_version 16 4 타임스탬프 상위 12비트 + 4비트 버전
clock_seq_hi_res 8 2 변형 비트 + 클록 시퀀스 상위
clock_seq_low 8 2 클록 시퀀스 하위
node 48 12 노드 ID (v1에서는 MAC 주소, 그 외에는 무작위)
버전은 time_hi_and_version 필드의 최상위 4비트(13번째 16진수 문자)에 인코딩됩니다. 변형은 clock_seq_hi_res의 최상위 2~3비트(17번째 16진수 문자)에 인코딩됩니다.
UUID 버전별 상세 설명
버전 1 — 시간 기반
UUID v1은 60비트 타임스탬프(1582년 10월 15일 이후 100나노초 간격)와 호스트의 MAC 주소, 그리고 클록 시퀀스를 결합합니다.
예시: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
장점: 시간 정보가 포함되어 생성 시간 순서로 대략적으로 정렬할 수 있습니다.
단점: MAC 주소가 포함되어 개인정보 문제가 있습니다. UUID가 언제, 어디서 생성되었는지 알 수 있습니다. 공개 식별자에는 권장되지 않습니다.
버전 2 — DCE 보안
UUID v2는 타임스탬프의 일부를 POSIX UID/GID와 도메인 식별자로 대체합니다. DCE 1.1 사양에 정의되어 있지만, 고유성 보장을 희생하여 도메인을 임베딩하기 때문에 실제로는 거의 사용되지 않습니다.
버전 3 — 이름 기반 (MD5)
UUID v3은 네임스페이스 UUID와 이름을 MD5로 해싱하여 결정론적 UUID를 생성합니다.
예시: 5df41881-3aed-3515-88a7-2f4a814cf09e (네임스페이스 DNS + "example.com")
사용 사례: 동일한 논리적 리소스에 대해 안정적이고 재현 가능한 식별자를 생성합니다. 동일한 네임스페이스와 이름을 두 번 해싱하면 항상 동일한 UUID를 얻습니다.
주의: MD5는 암호화적으로 약합니다. 새 시스템에는 v5를 권장합니다.
버전 4 — 무작위 (가장 많이 사용)
UUID v4는 122비트의 암호화적으로 안전한 무작위 데이터를 사용합니다(나머지 6비트는 버전과 변형에 사용).
예시: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
이것은 가장 널리 사용되는 UUID 버전입니다. 조정, 네트워크 접근, 호스트에 대한 지식이 전혀 필요 없습니다. 무작위성은 CSPRNG(암호화적으로 안전한 의사 난수 생성기)에서 옵니다.
버전 5 — 이름 기반 (SHA-1)
UUID v5는 개념적으로 v3과 동일하지만 MD5 대신 SHA-1을 사용합니다.
예시: 886313e1-3b8a-5372-9b90-0c9aee199e5d (네임스페이스 DNS + "example.com")
사용 사례: 결정론적이고 재현 가능한 UUID가 필요하고 MD5보다 강한 해시를 원할 때. SHA-1은 모든 목적에 암호화적으로 안전하지는 않지만, 충돌 저항성은 MD5보다 훨씬 뛰어납니다.
버전 7 — 시간 순서 (데이터베이스에 최적)
UUID v7은 RFC 9562의 핵심 기능입니다. 가장 중요한 비트에 **48비트 Unix 타임스탬프(밀리초 단위)**를 임베딩하여 UUID를 k-정렬 가능하게 만듭니다. 나중에 생성된 UUID는 먼저 생성된 UUID 다음에 정렬됩니다.
예시: 018e8f5a-2b3c-7d4e-8f9a-0b1c2d3e4f50
처음 12개의 16진수 문자(018e8f5a-2b3c)가 밀리초 타임스탬프를 인코딩하고 나머지는 무작위입니다. 이로 인해 v7은 데이터베이스 기본 키로 이상적입니다. v4의 전역 고유성을 유지하면서 삽입 순서가 보장되어 B-트리 인덱스 단편화가 크게 줄어듭니다.
충돌 확률: 생일 문제
UUID v4는 122비트의 무작위성을 사용합니다. 충돌 확률은 생일 문제 공식으로 추정할 수 있습니다:
p(n) ≈ 1 − e^(−n²/2N)
여기서 N = 2^122 ≈ 5.32 × 10^36 (UUID의 총 가능한 수).
충돌 확률이 50%가 되려면 약 **2.71 × 10^18(27.1 경)**개의 UUID를 생성해야 합니다. 초당 10억 개의 UUID 속도로 생성하면 약 86년이 걸립니다.
실제로 UUID v4의 충돌은 매우 발생하기 어려워서 모든 실제 애플리케이션에서 불가능한 것으로 간주할 수 있습니다.
UUID vs. 자동 증가 ID
| 기능 | UUID v4 | 자동 증가 |
|---|---|---|
| 고유성 범위 | 전역 | 로컬 (테이블별) |
| 생성 위치 | 클라이언트 측 | 서버 측 |
| 예측 가능성 | 예측 불가 | 순차적으로 예측 가능 |
| URL 안전성 | 있음 (인코딩 필요) | 있음 |
| DB 인덱스 성능 | 낮음 (무작위) | 우수 (순차적) |
| 병합/복제 | 용이 | 충돌 발생 가능 |
| 스토리지 크기 | 16바이트 (이진) | 4~8바이트 |
| 사람이 읽을 수 있는 정도 | 낮음 | 높음 |
| 보안 (열거 공격) | 안전 | 취약 |
자동 증가 ID는 분산 요구사항이 없는 단일 데이터베이스 애플리케이션에는 완벽히 적합합니다. UUID는 다음과 같은 경우에 유리합니다:
- 여러 서비스나 데이터베이스가 독립적으로 ID를 생성해야 할 때.
- 다른 소스의 레코드를 병합해야 할 수 있을 때.
- URL에서 순차적 ID 노출을 피하고 싶을 때 (열거 공격 방지).
데이터베이스 성능: v4 vs. v7
데이터베이스에서 UUID v4의 문제
UUID v4는 무작위이기 때문에 새 행이 B-트리 인덱스의 무작위 위치에 삽입됩니다. 이로 인해:
- 인덱스 페이지 분할 — 데이터베이스가 순서 없는 삽입을 수용하기 위해 인덱스 페이지를 자주 분할해야 합니다.
- 캐시 스래싱 — 무작위 접근 패턴이 버퍼 풀을 무효화하여 빈번한 디스크 읽기를 유발합니다.
- 쓰기 증폭 — 순차적 삽입보다 훨씬 많은 I/O.
대형 테이블(1000만 행 이상)에서의 벤치마크는 UUID v4 기본 키가 삽입이 많은 워크로드에서 순차적 키보다 3~5배 느린 경우가 많음을 보여줍니다.
UUID v7이 이를 해결하는 방법
UUID v7은 동일한 밀리초 창 내에서 단조 증가합니다. 새 레코드는 자동 증가와 마찬가지로 인덱스의 끝 또는 끝 근처에 삽입됩니다. 결과적으로:
- 거의 제로에 가까운 인덱스 단편화.
- 최적의 버퍼 풀 활용.
- 자동 증가에 필적하는 삽입 성능.
권장사항: 전역적으로 고유한 기본 키가 필요한 새 데이터베이스 스키마에는 UUID v7을 사용하세요.
분산 시스템 사용 사례
UUID는 분산 ID의 기반입니다:
- 마이크로서비스: 각 서비스가 중앙 시퀀스 서버와 통신하지 않고 독립적으로 ID를 생성할 수 있습니다.
- 이벤트 소싱: 이벤트는 재생 및 재구성 시에도 고유한 불변 ID를 얻습니다.
- CQRS: 클라이언트 생성 UUID로 명령과 쿼리를 상관시킬 수 있습니다.
- 멀티 리전 데이터베이스: 서로 다른 리전에서 생성된 레코드가 절대 충돌하지 않아 최종 일관성과 병합 작업이 간단해집니다.
- 멱등성 키: API는 클라이언트 생성 UUID를 멱등성 키로 사용하여 요청을 안전하게 재시도할 수 있습니다.
- 콘텐츠 주소 지정 시스템: UUID v3/v5는 시스템 전반에 걸쳐 동일한 리소스에 대한 안정적인 식별자를 생성할 수 있습니다.
ULID: 현대적인 대안
ULID(Universally Unique Lexicographically Sortable Identifier)는 UUID의 커뮤니티 개발 대안으로, 기본적으로 URL 안전하며 항상 정렬 가능합니다.
| 기능 | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| 정렬 가능 | 아니오 | 예 | 예 |
| URL 안전 | 인코딩 필요 | 인코딩 필요 | 예 (Crockford Base32) |
| 시간 구성요소 | 없음 | 있음 (ms) | 있음 (ms) |
| 표준 | RFC 9562 | RFC 9562 | 커뮤니티 사양 |
| 형식 길이 | 36자 | 36자 | 26자 |
| 밀리초 내 단조 증가 | 아니오 | 선택적 | 예 |
ULID는 01ARZ3NDEKTSV4RRFFQ69G5FAV처럼 생겼습니다 — 26자, 하이픈 없음, URL에서 직접 사용 가능. RFC 호환성을 걱정하지 않고 정렬 가능성과 URL 안전성을 바로 사용하고 싶다면 ULID를 고려해볼 만합니다.
다양한 언어에서 UUID 생성하기
JavaScript / TypeScript (Node.js 및 브라우저)
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid';
const randomId = uuidv4(); // "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
const sortableId = uuidv7(); // "018e8f5a-2b3c-7d4e-8f9a-0b1c2d3e4f50"
// 네이티브 crypto (Node 14.17+ / 모던 브라우저)
const nativeId = crypto.randomUUID(); // UUID v4
Python
import uuid
# v4 — 무작위
print(uuid.uuid4()) # "f47ac10b-58cc-4372-a567-0e02b2c3d479"
# v5 — 네임스페이스 + 이름에서 결정론적 생성
print(uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com'))
# "886313e1-3b8a-5372-9b90-0c9aee199e5d"
Go
import "github.com/google/uuid"
id := uuid.New() // UUID v4
v7, _ := uuid.NewV7() // UUID v7 (google/uuid v1.6+)
fmt.Println(id.String())
Java
import java.util.UUID;
UUID v4 = UUID.randomUUID();
System.out.println(v4); // "3e4666bf-d5e5-4aa7-b8ce-cefe41c7568a"
Rust
use uuid::Uuid;
let v4 = Uuid::new_v4();
let v7 = Uuid::now_v7();
println!("{}", v4);
println!("{}", v7);
모범 사례
- 범용 고유 식별자에는 v4 사용 — 순서가 중요하지 않을 때 v4가 가장 간단합니다.
- 데이터베이스 기본 키에는 v7 사용 — 인덱스 단편화를 피하고 자연스러운 정렬 순서를 얻을 수 있습니다.
- 결정론적 ID에는 v5 사용 — 동일한 입력이 항상 동일한 UUID를 생성해야 할 때 (예: URL로 콘텐츠 중복 제거).
- 개인정보에 민감한 상황에서 v1 피하기 — MAC 주소와 생성 시간이 포함됩니다.
- 데이터베이스에서 UUID를 이진(16바이트)으로 저장 — varchar(36) 대신, 공간을 절약하고 인덱스 성능을 향상시킵니다.
- DCE Security를 구현하지 않는 한 v2 사용 금지.
- API 경계에서 UUID 입력 유효성 검사 — 형식이 잘못된 식별자 주입을 방지하기 위해.
- 잘 테스트된 라이브러리 사용 — 자체 UUID 생성을 구현하는 대신, 엔트로피 소스는 실수하기 쉽습니다.
자주 묻는 질문
Q: UUID와 GUID는 같은 것인가요?
A: 기능적으로는 같습니다. GUID는 마이크로소프트가 동일한 128비트 식별자 형식에 붙인 이름입니다. 구조는 동일하고 용어만 다릅니다.
Q: 두 UUID가 동일할 수 있나요?
A: 이론적으로는 가능하지만, v4의 경우 확률이 무시할 수 있을 만큼 작습니다. 충돌 확률이 50%가 되려면 27.1경 개의 UUID를 생성해야 합니다. 실제로는 충돌을 만나지 않을 것입니다.
Q: 데이터베이스 기본 키에는 어떤 UUID 버전을 사용해야 하나요?
A: UUID v7이 최선의 선택입니다. 시간 순서(B-트리 인덱스에 유리), 전역 고유성, RFC 9562 표준화의 장점이 있습니다. 라이브러리가 아직 v7을 지원하지 않으면 ULID나 "COMB" UUID 패턴을 대안으로 사용하세요.
Q: UUID v4는 세션 토큰으로 사용하기에 충분히 안전한가요?
A: UUID v4는 122비트의 무작위성이 있어 일반적으로 충분합니다. 그러나 전용 세션 토큰 라이브러리는 약간 더 많은 엔트로피나 더 나은 인코딩을 사용할 수 있습니다. 고보안이 필요한 경우 전용 토큰 생성기를 사용하세요.
Q: SQL 데이터베이스에서 UUID를 효율적으로 저장하려면 어떻게 해야 하나요?
A: 가능하면 네이티브 UUID 컬럼 유형(PostgreSQL, MySQL 8+)을 사용하거나 BINARY(16) 컬럼을 사용하세요. 2.25배 더 많은 공간을 차지하고 인덱싱이 느린 CHAR(36) / VARCHAR(36)는 피하세요.
Q: UUID v3과 v5의 차이점은 무엇인가요?
A: 둘 다 이름 기반이고 결정론적입니다. v3은 MD5를 사용하고 v5는 SHA-1을 사용합니다. SHA-1이 더 나은 충돌 저항성을 가지므로 새 시스템에는 v5가 권장됩니다. 둘 다 보안에 중요한 해싱에는 사용하면 안 됩니다.
Q: UUID v7이 UUID v4를 대체하나요?
A: 데이터베이스 기본 키로서는 예 — v7이 v4보다 엄격히 우수합니다. 순서가 관련 없는 다른 사용 사례(API 키, 토큰, 상관 ID 등)에는 v4가 여전히 완벽히 적합합니다.
요약
UUID는 현대 소프트웨어 엔지니어링의 기본 요소입니다. 버전 간의 차이를 이해하면 올바른 트레이드오프를 할 수 있습니다:
- v4 — 조정 없이 범용 고유성을 위한 최선의 선택.
- v5 — 결정론적이고 재현 가능한 식별자를 위한 최선의 선택.
- v7 — 시간 순서로 인해 데이터베이스 기본 키를 위한 최선의 선택.
모놀리스, 마이크로서비스 아키텍처, 전 세계적으로 분산된 데이터베이스 중 무엇을 구축하든, UUID는 데이터를 고유하고 안전하게 식별하기 위한 실전 검증된 표준화된 방법을 제공합니다.