UUID v7 생성기
Generate time-ordered UUID v7 for database primary keys
…
포맷
UUID v7이란 무엇인가요?
UUID v7은 RFC 9562(2024년 5월)에서 표준화된 차세대 UUID 형식입니다. 최상위 비트에 48비트 Unix 밀리초 타임스탬프를 인코딩하고, 버전 및 변형 마커를 뒤따르며, 나머지 비트를 암호학적으로 안전한 무작위 데이터로 채웁니다.
타임스탬프가 상위 비트를 차지하기 때문에 UUID v7 값은 시간순으로 정렬됩니다——사전순으로도 수치적으로도. 이로 인해 무작위 UUID(v4)가 B-tree 인덱스 단편화를 유발하는 데이터베이스 기본 키로 탁월한 선택입니다.
무작위 UUID가 데이터베이스 인덱스를 단편화시키는 이유
B-tree 인덱스——PostgreSQL, MySQL, SQLite 및 대부분의 다른 데이터베이스에서 사용——는 행을 키 값으로 정렬된 상태로 유지합니다. 새 행을 삽입할 때 데이터베이스는 인덱스 트리에서 올바른 정렬 위치에 배치해야 합니다.
UUID v4(완전히 무작위)의 경우 각 새 삽입은 인덱스 트리의 본질적으로 무작위 위치에 떨어집니다. 이로 인해 데이터베이스는 내부 인덱스 페이지를 지속적으로 읽고 다시 쓰며, 가득 찬 페이지를 분할하고 다른 페이지를 절반 비어있게 만듭니다. 결과는 테이블이 커짐에 따라 쓰기와 읽기 모두 느리게 만드는 단편화되고 비대해진 인덱스입니다.
UUID v7 비트 레이아웃
UUID v7은 128비트 너비로 6개 필드로 나뉩니다:
| 비트 | 필드 | 목적 |
|---|---|---|
| 48 | unix_ts_ms | 밀리초 단위 48비트 Unix 타임스탬프——전체 상위 절반을 차지 |
| 4 | ver | 버전 번호——항상 0111(10진수 7) |
| 12 | rand_a | 12비트 암호학적으로 안전한 무작위 데이터 |
| 2 | var | 변형 마커——항상 10(RFC 4122 변형) |
| 62 | rand_b | 62비트 암호학적으로 안전한 무작위 데이터 |
타임스탬프 정밀도는 1밀리초입니다. 같은 밀리초 내에서 UUID v7 값은 무작위 접미사로 정렬됩니다——밀리초 이하의 단조 증가는 보장되지 않지만 k-sortable입니다: 시간적으로 가깝게 생성된 ID는 인덱스에서도 가깝게 정렬됩니다.
UUID v7 대 UUID v1
UUID v1과 UUID v7 모두 타임스탬프를 내장하지만 설계가 크게 다릅니다:
| 기능 | UUID v7 | UUID v1 |
|---|---|---|
| 에포크 / 시간 기준 | Unix 에포크(1970년 1월 1일) | 그레고리안 에포크(1582년 10월 15일) |
| 시간 정밀도 | 1밀리초 | 100나노초 |
| 정렬 가능 | 예——설계상 k-sortable | 아니오——시간 필드가 UUID 레이아웃에서 뒤섞임 |
| 프라이버시 | 호스트 정보 누출 없음 | 생성 호스트의 MAC 주소 내장 |
| DB 인덱스 성능 | 탁월——순차 삽입, 단편화 최소 | 불량——타임스탬프에도 불구하고 비순차 |
| 표준 | RFC 9562(2024) | RFC 4122(2005) |
| 네이티브 브라우저 지원 | 아직 없음(crypto.randomUUID v7 없음) | 네이티브로 사용 불가 |
시간 순서 UUID가 필요한 새 프로젝트에서는 UUID v1보다 UUID v7을 선호하세요. UUID v1은 레거시이며 호스트 정보를 누출합니다.
UUID v7 대 ULID
ULID(Universally Unique Lexicographically Sortable Identifier)는 UUID v7과 유사한 문제를 해결합니다. 비교:
- RFC 9562 UUID 표준 준수——모든 UUID 도구와 호환
- 하이픈 16진 형식——보편적으로 인식됨
- 네이티브 데이터베이스 UUID 열 지원
- 총 128비트
- Crockford Base32 인코딩——26자, 약간 더 압축
- 대소문자 구분 없고 모호한 문자(I, L, O, U) 회피
- 한눈에 더 읽기 쉬움
- 라이브러리 필요——네이티브 플랫폼 지원 없음
이미 UUID 생태계(PostgreSQL uuid 열, UUID를 반환하는 REST API)에 있다면 UUID v7을 사용하세요. 처음부터 시작하고 더 인간 친화적인 인코딩을 선호한다면 ULID가 합리적인 대안입니다.
데이터베이스에서 UUID v7 사용
UUID v7은 아직 대부분의 데이터베이스에서 네이티브로 생성되지 않지만 표준 UUID 열에 저장하고 애플리케이션 코드 또는 확장을 통해 생성할 수 있습니다:
uuid 열에 저장합니다. DB 생성 ID가 필요하면 pg-uuidv7 확장이 uuid_generate_v7() 서버 측 함수를 추가합니다.BINARY(16) 또는 CHAR(36) 열에 저장합니다. 애플리케이션 코드에서 생성합니다. MySQL 8.0+는 v1에 대해 UUID_TO_BIN(UUID(), 1)을 통한 순서 있는 UUID를 지원하지만 v7은 앱 수준 생성이 필요합니다.TEXT(36자) 또는 BLOB(16바이트)로 저장합니다. 애플리케이션 코드에서 생성합니다. UUID v7은 고정 너비 타임스탬프 접두사를 사용하므로 TEXT의 사전순 정렬이 올바르게 작동합니다.코드 예제
UUID v7은 아직 crypto.randomUUID()로 사용할 수 없습니다. 네이티브 지원이 올 때까지 uuidv7(npm)와 같은 라이브러리를 사용하세요:
function generateUuidV7() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
// Embed 48-bit Unix ms timestamp
buf[0] = Number((ms >> 40n) & 0xFFn)
buf[1] = Number((ms >> 32n) & 0xFFn)
buf[2] = Number((ms >> 24n) & 0xFFn)
buf[3] = Number((ms >> 16n) & 0xFFn)
buf[4] = Number((ms >> 8n) & 0xFFn)
buf[5] = Number(ms & 0xFFn)
// Set version 7 (0111xxxx)
buf[6] = (buf[6] & 0x0F) | 0x70
// Set variant (10xxxxxx)
buf[8] = (buf[8] & 0x3F) | 0x80
const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}
// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto' // v4 only — no v7 yet in stdlib# pip install uuid7 import uuid_extensions uid = uuid_extensions.uuid7() print(uid) # e.g. 018e2b3d-1a2b-7000-8000-abc123456789 print(uid.time) # Unix ms timestamp embedded in the UUID # Or as a plain string from uuid_extensions import uuid7str print(uuid7str())
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
SELECT encode(
set_bit(
set_bit(
overlay(
uuid_send(gen_random_uuid())
PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
FROM 1 FOR 6
),
52, 1
),
53, 1
),
'hex'
)::uuid;
$$;
-- Usage as a default primary key
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
payload jsonb,
created_at timestamptz DEFAULT now()
);function extractTimestamp(uuid: string): Date {
const hex = uuid.replace(/-/g, '')
const ms = parseInt(hex.slice(0, 12), 16) // first 48 bits = ms timestamp
return new Date(ms)
}
const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"