본문으로 건너뛰기
yutils

UUID vs ULID — 어떤 식별자를 골라야 하나

UUID v4·v7, ULID, Snowflake 를 정렬성·DB 성능·URL 친화성·충돌 위험 관점에서 비교.

약 8분 읽기

분산 환경에서 식별자를 만들 때 선택지는 의외로 많다. UUID v4·v7, ULID, Snowflake, 짧은 nanoid 같은 변형들. 각자 "정렬되는가", "DB 에 친화적인가", "URL 에 박을 수 있는가", "추측 가능한가" 라는 축 에서 트레이드오프를 가진다. 이 가이드는 실무에서 자주 부딪히는 다섯 선택지를 비교하고, 어느 경우에 어떤 것이 맞는지 정리한다.

왜 식별자 선택이 중요한가

식별자는 한 번 박으면 마이그레이션 비용이 크다 — DB row 만 바꾸는 게 아니라 외부에 노출된 URL, 로그, 분석 데이터, 백업까지 추적해야 한다. 그리고 식별자의 성격(정렬성·길이·생성 위치)이 다음 항목에 직접 영향을 준다.

  • DB 인덱스 효율 — B-tree 인덱스는 정렬된 키에 친화적 이다. 랜덤 UUID v4 를 PK 로 박으면 insert 마다 인덱스 페이지가 흩어져 쓰기 성능과 캐시 효율이 떨어진다.
  • URL 보안 — Auto-increment 정수 id 는 enumeration 공격 (다음 ID 추측) 에 취약. 충분히 큰 임의값이 필요한 경우가 많다.
  • 로깅·디버깅 — 시간순으로 정렬되는 ID 는 "최근 만든 것" 을 한 눈에 본다. 랜덤 ID 는 로그 분석에 timestamp 가 추가로 필요.

5 가지 후보

1. UUID v4 (랜덤)

f47ac10b-58cc-4372-a567-0e02b2c3d479
  • 구조 — 122 비트 임의 + 6 비트 버전/variant. 128 비트.
  • 형식 — 32 hex 글자 + 4 하이픈 = 36 자.
  • 정렬 — 불가능 (완전 랜덤).
  • 충돌 확률 — 사실상 0 (122 비트, 1 조개 생성해도 충돌 기대값 0.0000003).
  • 장점 — 어디서나 안전. 클라이언트·서버·DB 어디서 만들든 충돌 X. 라이브러리 보편적.
  • 단점 — DB PK 로 박으면 인덱스 fragmentation. 36 자 길이.

2. UUID v7 (timestamp + 랜덤)

018f5b86-7c4d-7abc-9def-0123456789ab
  • 구조 — 48 비트 Unix epoch 밀리초 + 12 비트 임의 + 62 비트 임의. RFC 9562 (2024).
  • 형식 — UUID v4 와 동일한 36 자.
  • 정렬 — 시간순 단조 증가 (밀리초 단위). 같은 ms 내에서는 랜덤.
  • 충돌 확률 — 같은 ms 안에서 74 비트 임의 — 사실상 0.
  • 장점 — UUID v4 의 호환성 + 정렬 가능. DB 인덱스 친화적. 신규 시스템 기본값으로 권장.
  • 단점 — 발급 시각이 노출됨 (보안상 ID 가 언제 만들어졌는지 알리고 싶지 않다면 v4 또는 별도 칼럼).

3. ULID

01HQXM5K3D7Z9YW0123456789AB
  • 구조 — 48 비트 Unix epoch 밀리초 + 80 비트 임의 = 128 비트.
  • 형식 — Crockford Base32, 26 자. 하이픈 없음.
  • 정렬 — 시간순 사전식 정렬 (lexicographic) — 문자열 정렬만으로 시간 순서.
  • 장점 — URL 친화 (영숫자만). UUID v4 보다 28% 짧음. ULID 와 UUID 사이 1:1 변환 가능 (둘 다 128 비트).
  • 단점 — DB native 타입 없음 (대부분 VARCHAR 26 또는 BINARY 16). UUID v7 의 표준화 이후 ULID 의 상대 가치 약간 감소.

4. Snowflake (Twitter)

1768956123987456000
  • 구조 — 41 비트 timestamp + 10 비트 머신 ID + 12 비트 시퀀스 = 63 비트 (signed 64-bit 정수).
  • 형식 — 정수 — 보통 19 자 문자열.
  • 정렬 — 시간순 단조 증가.
  • 장점 — 정수 인덱스가 가장 빠름. BIGINT 8 바이트로 저장. URL 짧음.
  • 단점 — 머신 ID 할당 인프라 필요. 분산 환경에서 시계 역행 대응 코드 필요. 클라이언트 측에서 생성 불가 (머신 ID 충돌).

5. nanoid (짧은 랜덤)

V1StGXR8_Z5jdHi6B-myT
  • 구조 — 기본 21 자, 126 비트 임의. URL-safe 알파벳.
  • 정렬 — 불가능.
  • 장점 — UUID v4 보다 짧으면서 보안 강도 동일.
  • 단점 — DB 표준 타입 없음. 디코딩 도구 적음.

한눈에 비교

UUID v4UUID v7ULIDSnowflake
길이36 자36 자26 자~19 자
비트122~74 + ms80 + ms~12 + ms
정렬XO (ms)O (ms)O
시각 노출XOOO
분산 생성OOO인프라 필요
DB 타입UUIDUUIDBINARY/VARCHARBIGINT

실무 선택 가이드

새 시스템의 PK

UUID v7 권장. UUID 표준 타입을 그대로 쓰면서 시간순 정렬이 되어 인덱스 fragmentation 이 거의 없다. 라이브러리는 Node 의 uuid@9+, Python 의 uuid_utils, Go 의 google/uuid@1.6+ 가 v7 을 지원. UUID / ULID 생성기 도 v7 생성 지원.

외부 노출 URL

시각 노출이 보안 문제이면 (예: 가입한 시간이 알려지면 안 됨) UUID v4 또는 nanoid. 그렇지 않으면 UUID v7 또는 ULID. auto-increment 정수는 외부 노출 금지 — 다음 ID 추측 + 데이터 양 추정 가능.

로그 / 이벤트 ID

ULID 추천. 짧고 (26 자), 시간 정렬, URL 친화. 로그 조회에 timestamp 칼럼 따로 안 둬도 ID 자체로 시간 정렬.

고성능 DB PK

Snowflake 또는 BIGINT auto-increment. UUID v7 도 충분히 빠르지만, PostgreSQL BIGINT (8 바이트) vs UUID (16 바이트) 인덱스 크기 차이는 대규모에서 의미가 있다. 클라이언트 생성이 필요 없는 백엔드 시스템 한정.

클라이언트 생성 ID (offline-first)

UUID v4 또는 v7. 머신 ID 할당 인프라 없이도 충돌 0. 모바일/PWA 가 오프 라인에서 만든 ID 를 서버로 동기화하는 패턴에 표준.

자주 빠지는 함정

1. UUID v1 (MAC + timestamp)

오래된 시스템에서 보이지만 권장하지 않는다. MAC 주소가 노출돼 프라이버 시 이슈. 같은 머신에서 빠르게 생성하면 충돌 가능. v7 이 사실상 후속.

2. UUID 문자열로 저장

PostgreSQL/MySQL 의 UUID 타입은 16 바이트 binary 저장. 문자열 (36 자) 로 저장하면 인덱스 크기 2.25 배, 쿼리도 느려진다. 항상 native UUID 타입 사용.

3. ULID 의 monotonic 보장 오해

같은 ms 안에서 ULID 가 단조 증가하려면 라이브러리의 monotonic 모드를 명시 활성화해야 한다. 그렇지 않으면 같은 ms 내에서 랜덤 — 정렬은 ms 단위까지만 보장.

4. 충돌 가능성 과대평가

UUID v4 의 122 비트 임의는 1 초당 100 만 개 생성을 100 년 해도 50% 충돌 확률이 안 된다. "혹시 충돌하면" 걱정으로 PK 에 UNIQUE 제약 + 재시도 로직을 박는 건 대개 과한 방어. 단, 외부 시드를 받아 생성하는 경우 (예: deterministic UUID v5) 는 별개.

도구로 직접 보기

요약

  • 새 PK 의 기본값은 UUID v7. UUID 호환성 + DB 인덱스 친화 + 분산 생성.
  • 외부 노출 URL — 시각 노출 OK 면 v7/ULID, 보안 우선이면 v4 또는 nanoid.
  • 로그/이벤트 — ULID 가 짧고 정렬되어 편함.
  • UUID 는 항상 native binary 타입으로 저장 — 문자열 저장은 2.25 배 낭비.
  • auto-increment 정수 ID 를 외부 URL 에 박지 마라 — enumeration 공격.
가이드 목록으로