UUID와 ULID는 분산 시스템이나 데이터베이스에서 ‘고유한 ID’를 발급하기 위한 메커니즘이다. 얼핏 비슷해 보이지만 표준화 여부, 저장 효율, 사람이 읽기 쉬운지 여부, 노출 리스크 등에서 차이가 있다. 대표적인 세 가지 — UUID v4, UUID v7, ULID — 의 특징과 선택 기준을 정리하고, 마지막에 UUID v1 / v6도 간단히 살펴본다.

각 ID의 생성과 시각 정보 복원에 대응한 브라우저 전용 도구는 다음과 같다.


UUID v4: 완전 랜덤형

UUID v4는 128bit 중 122bit를 난수로 채워 충돌 확률이 극도로 낮다. 반면 시계열 순으로 정렬되지 않기 때문에 B-Tree 계열 인덱스에서는 페이지 분할이 잦아지고 삽입 지역성이 악화되기 쉽다. 세션 ID나 원타임 키처럼 ‘완전 랜덤이면 충분한’ 용도에 적합하다.


UUID v7: 시계열 정렬이 가능한 정통 진화형

2024년에 RFC 9562로 표준화된 UUID v7은 **선두 48bit에 UNIX epoch 시각(ms)**을 담고, 나머지는 난수로 채운다. 사전순 비교(문자열 비교) = 생성 순서(시계열)이 되므로 주 키로 사용해도 인덱스 지역성이 좋다. 기존의 uuid 타입이나 BINARY(16)에 그대로 저장할 수 있는 점도 강점이다.

  • 난수 비트 길이: v7은 version/variant 비트를 제외하면 대략 74bit 남짓을 난수로 사용한다. 구현에 따라 같은 밀리초 내 충돌을 피하려고 모노토닉(monotonic) 생성 과정에서 난수 일부를 조정하기도 하지만, 통계적으로 충분한 무작위성을 갖는다.
  • 정보 노출 리스크: ID에서 **생성 시각(ms 단위)**을 추정할 수 있다. 발행량이 많고 난수 구현이 약한 경우에는 근처 ID를 추측하기 쉬워질 수 있다. 내부 시스템에서는 보통 허용되지만, 공개 엔드포인트에서 연속성 노출을 싫어한다면 주의해야 한다.

ULID: 사람이 친근한 ID

ULID는 2016년에 제안된 사실상(de facto) 표준으로, Crockford Base32 26자로 표시한다. 혼동되기 쉬운 문자(O/I/L/1)를 제외해 URL 포함이나 복사에 강하다. UUID v7과 마찬가지로 **선두 48bit에 시각(ms)**을 담으므로 문자열 사전순 = 시계열 순이 된다.

  • 정렬 보장: ULID 문자열끼리 사전순 비교를 하면 항상 시계열 순이 유지된다. 모노토닉 생성(monotonic ULID) 사양도 널리 쓰인다.
  • 저장 형태: TEXT(26)으로 가독성을 살리기 쉽지만, DB에서 바이너리로 저장하려면 Base32 ↔ 16B 변환이 필요하다. 128bit 폭의 바이너리로 취급할 수 있지만, RDB의 UUID 전용 타입과는 비호환인 경우가 많다.
  • 정보 노출 리스크: v7과 마찬가지로 ms 단위 시각이 드러난다. 공개 API 리소스 ID 등에서 발행 순서를 추측당하고 싶지 않다면 신중히 판단해야 한다.

세 방식 비교표 (실무 관점)

항목 UUID v4 UUID v7 ULID
표준화 RFC 4122 RFC 9562(2024) 비표준(de facto)
비트 구성 128bit 중 122bit 난수 48bit = UNIX ms + 나머지 난수(모노토닉 대응 가능) 48bit = UNIX ms + 80bit 난수
난수 엔트로피 ≈122bit ≈74bit(버전/배리언트 제외한 개산치) 80bit
문자열 표기 36자(16진수 + 하이픈) 36자(16진수 + 하이픈) 26자(Crockford Base32)
문자열 자연 정렬 × (랜덤) ○ (사전순 = 시계열) ○ (사전순 = 시계열)
바이너리 자연 정렬 × ○ (BINARY(16) memcmp로 상승) ○ (선두 6byte를 빅엔디언으로 두면 memcmp로 상승)
DB 저장 성질 uuid 타입 / BINARY(16)이 최적 uuid 타입 / BINARY(16)이 최적 TEXT(26)은 가독성, BINARY(16)은 변환 필요 / UUID 타입과는 비호환
인덱스 지역성 나쁨 (무작위 삽입으로 분할) 좋음 (말단 추기 / 핫스팟 주의) 좋음 (말단 추기 / 핫스팟 주의)
등가 검색 비용 낮음 (16B 비교) 낮음 (16B 비교) TEXT는 비교적 높음(collation 영향) / BINARY는 낮음
인코딩 부담 16진 ↔ 16B(가벼움) 16진 ↔ 16B(가벼움) Base32 ↔ 16B(약간 무거움)
가독성/URL 적합성 낮음 낮음 높음
정보 노출(생성 시각) 없음 있음 (ms) 있음 (ms)
주요 용도 세션 ID, 원타임 키 DB 주 키, 이벤트 ID, 로그 시계열 URL·공개 ID, 가시성 높은 로그

보충 (v7/ULID 핫스팟): 단일 샤드·단일 리더에 말단 추기가 집중되면 B-Tree 끝단이 과열될 수 있다. 대응책으로 상위 비트를 조금 섞는(prefix shuffle) 방식이나 샤딩 키 병행을 검토하자.


DB 구현 메모 (PostgreSQL / MySQL / SQLite)

  • PostgreSQL
    • v4/v7: uuid 타입이 최적. v7은 문자열 → uuid로 넣으면 정렬성을 활용할 수 있다.
    • ULID: char(26)/text 또는 bytea(16)(Base32 변환)을 사용.
  • MySQL/InnoDB
    • BINARY(16)이 빠르고 공간도 절약(v4/v7). v7은 빅엔디언 그대로 비교하면 시계열 순으로 정렬된다. ULID는 CHAR(26) 또는 BINARY(16)+변환.
  • SQLite
    • 전용 UUID 타입이 없다. BLOB(16) 또는 TEXT로 운용한다. 인덱스된 BLOB 비교는 비교적 효율적이다.

보안 관점 보강

  • 추측 저항성: v7/ULID는 시각 비트가 공통이라, 같은 밀리초에 대량 발행하고 난수 구현이 약하면 근접 ID 추측 리스크가 상대적으로 커진다. 암호학적 PRNG를 사용하고, 모노토닉 증가의 편향을 최소화해야 한다.
  • 메타데이터 유출: ID로부터 생성 시각이나 대략적 발행량이 유추될 수 있다. 공개 API에서는 내부 ID와 외부 ID를 분리하는 설계가 안전하다.

정리 (선택 가이드)

  • UUID v4: 완전 랜덤. 정렬성 없음. 이론적으로 미세한 충돌 가능성은 남는다. 세션 ID나 CSRF 토큰 등에 적합.
  • UUID v7: 순서가 보장되는 표준화 UUID. 내부 시스템이나 DB 주 키에 최적. 모노토닉 생성으로 충돌 위험을 실무적으로 회피할 수 있다.
  • ULID: (다른 방식보다) 사람이 읽기 조금 더 쉬움. URL이나 로그 가시성이 중요한 경우 유력하지만, 내부 주 키 용도에서는 v7이 나은 경우가 많다. 모노토닉 생성으로 충돌 위험을 현실적으로 회피 가능.

부록: UUID v1 / v6 (간단히)

  • UUID v1 (시간 + MAC) 60bit 타임스탬프(100ns 단위) + 노드 식별자(종종 MAC 주소)를 포함한다. 높은 시계열성이 있지만 MAC 기반으로 호스트 정보가 노출될 우려가 있고, 클록 역행(clock rollback) 처리 등 구현 주의점이 많다. 현대에는 프라이버시 문제 때문에 권장되지 않는 경우가 많다.

  • UUID v6 (v1 재배열판) v1의 타임스탬프를 빅엔디언으로 재배열해 사전순 정렬성을 높인 제안이다. 역사적으로는 ‘정렬하기 쉬운 v1’로 주목받았으나, 표준화 논의는 결국 v7로 귀결됐다. 신규 설계에서는 v6 대신 v7을 선택하는 것이 일반적이다.


부록: 구현 체크리스트

  • 난수는 암호학적 PRNG(OS가 제공하는 안전한 소스)를 사용한다.
  • v7/ULID의 모노토닉 생성은 같은 밀리초 내 충돌을 피하면서도 난수 편향을 최소화한다.
  • DB는 uuid 타입 / BINARY(16)를 우선해 **문자열 컬레이션(collation)**으로 인한 성능 저하를 피한다.
  • 외부 공개 ID 정책(순서 노출 허용 여부)을 정하고, 필요하다면 내부 ID와 외부 ID를 분리한다.

결론적으로 현 시점에서는 내부 주 키는 UUID v7을 1순위로 검토하고, 가독성이 중요한 외부용이나 기술적 제약이 있는 경우 ULID를 고려하는 구도가 기본이다. 시각 노출을 피해야 하는 특수 요구가 있을 때만 v4나 다른 방식을 선택하게 될 것이다.