UUIDv4 Primary Keys 피하기

발행: (2025년 12월 15일 오후 07:08 GMT+9)
9 분 소요

Source: Hacker News

Introduction

지난 10년 동안, 기본 키 데이터 타입으로 UUID Version 41를 사용한 데이터베이스는 보통 성능이 좋지 않고 I/O가 과도하게 발생했습니다.

UUID는 PostgreSQL에서 기본 데이터 타입이며 바이너리 데이터로 저장될 수 있습니다. RFC에서 여러 버전이 정의되어 있습니다. Version 4는 대부분 무작위 비트로 구성되어 값이 생성된 시점이나 생성 위치와 같은 정보를 가릴 수 있습니다.

Version 4 UUID는 PostgreSQL 13(2020년 출시)부터 제공되는 gen_random_uuid()2 함수를 사용해 쉽게 생성할 수 있습니다.

UUID Version 4에 대한 오해가 존재한다는 것을 알게 되었고, 때때로 이러한 오해가 사용자가 이 데이터 타입을 선택하는 이유가 되기도 합니다.

성능 저하, 오해, 그리고 대안이 존재한다는 점 때문에 저는 간단한 입장을 갖게 되었습니다: 기본 키에 UUID Version 4를 사용하지 말라.

좀 더 논란이 될 수 있는 제 의견은 UUID 자체를 피하라는 것이지만, 실용적인 대안이 없는 정당한 이유가 있는 경우도 있다는 점을 이해합니다.

데이터베이스 애호가로서 이 고전적인 “정수 vs. UUID” 논쟁에 대해 명확한 입장을 제시하고 싶었습니다.

데이터베이스 관계자들 사이에서는 이 논쟁이 지겨우면서도 진부하게 느껴질 수 있습니다. 하지만 컨설팅 작업을 하면서도 2024년·2025년에 UUID v4를 사용하는 데이터베이스를 여전히 보게 되며, 여기서 논의하는 문제들은 여전히 관련성이 있습니다.

본격적으로 살펴보겠습니다.

UUID context for this post

  • UUID(또는 Microsoft 용어인 GUID)3는 36자(32개의 숫자와 4개의 하이픈)로 이루어진 긴 문자열이며, PostgreSQL에서는 바이너리 uuid 데이터 타입을 사용해 128‑bit(16‑byte) 값으로 저장됩니다.
  • RFC는 128비트가 어떻게 설정되는지를 문서화하고 있습니다.
  • UUID Version 4의 비트는 대부분 무작위 값입니다.
  • UUID Version 7은 첫 48비트에 타임스탬프를 포함하고 있어, 무작위 값에 비해 데이터베이스 인덱스와 훨씬 잘 맞습니다.

이 글을 쓰는 시점에서는 아직 공개되지 않았으며 PostgreSQL 17에서 잠시 제외되었지만, UUID v7은 2025년 가을에 출시 예정인 PostgreSQL 184에 포함될 예정입니다.

Scope of web app usage and their scale

여기서 다루는 웹 애플리케이션은 PostgreSQL을 주요 OLTP 데이터베이스로 사용하는 단일(monolithic) 웹 앱을 의미합니다. 일반적인 카테고리로는 소셜 미디어, 전자상거래, 클릭 트래킹, 비즈니스 프로세스 자동화 등이 있습니다.

논의되는 성능 문제는 비효율적인 저장 및 검색과 관련이 있으므로, 위 카테고리 전반에 적용됩니다.

Randomness is the issue

UUID Version 4의 핵심 문제는 122비트가 “무작위 또는 의사‑무작위 생성 값”1이며, 기본 키가 인덱스로 지원된다는 점에서 삽입 및 개별 항목·범위 검색 시 인덱스에 미치는 영향입니다.

무작위 값은 정수처럼 자연스러운 정렬이 없으며, 문자열처럼 사전식 정렬도 없습니다. UUID v4는 “바이트 순서”를 가지고 있지만, 이것이 접근 방식에 유용한 의미를 제공하지는 않습니다.

Why choose UUIDs at all? Generating values from one or more client applications

UUID를 사용하는 한 가지 경우는 클라이언트 혹은 여러 서비스에서 식별자를 생성한 뒤 PostgreSQL에 저장해야 할 때입니다.

각 서비스가 자체 데이터베이스를 갖는 마이크로서비스 아키텍처에서는 충돌 없이 식별자를 생성할 수 있다는 점이 UUID의 장점이 됩니다. 또한 UUID는 단순 정수와 달리 나중에 원본 데이터베이스를 식별하는 데도 활용될 수 있습니다.

충돌 방지를 위해( HN 토론5 참고) 시퀀스 기반 정수만으로는 실질적인 보장을 제공하기 어렵습니다. 해킹 방법으로는 두 인스턴스에서 짝수·홀수 정수를 사용하거나 int8 공간 내에서 별도 범위를 할당하는 방식이 있지만, 복잡도가 증가합니다.

복합 기본 키(CPK)와 같은 대체 식별자도 존재하지만, 서비스 간에 행을 고유하게 식별하기 위해서는 여전히 두 개 이상의 값이 필요할 수 있습니다.

위키피디아는 충돌 확률을 다음과 같이 설명합니다:

50 % 확률로 충돌이 발생하도록 무작위 version‑4 UUID를 생성해야 하는 개수: 2.71 quintillion
초당 10억 개의 UUID를 약 86년 동안 생성할 경우.

Misconceptions: UUIDs are secure

한 가지 오해는 UUID가 보안에 강하다는 것입니다. RFC는 명시적으로 UUID를 “보안 기능”으로 간주해서는 안 된다고 명시하고 있습니다.

RFC 4122, §6 – Security Considerations
UUID가 추측하기 어려운 것으로 가정하지 마세요; 보안 기능으로 사용해서는 안 됩니다.

Creating obfuscated values using integers

UUID v4가 생성 시점을 가릴 수 있지만, 값 자체를 정렬해 서로 언제 생성됐는지를 파악할 수는 없습니다. 비슷한 특성은 약간의 추가 작업을 통해 정수로도 구현할 수 있습니다.

한 가지 방법은 정수에서 의사‑무작위 코드를 생성한 뒤, 외부에 노출할 때는 그 값을 사용하고 내부에서는 여전히 정수를 유지하는 것입니다.

전체 구현 내용은 Short alphanumeric pseudo‑random identifiers in PostgreSQL6에 자세히 나와 있습니다. 요약은 다음과 같습니다:

  1. 십진 정수(예: 2)를 바이너리 비트(예: 4‑byte, 32‑bit 정수: 00000000 00000000 00000000 00000010)로 변환합니다.
  2. 비밀 키를 사용해 모든 비트에 XOR 연산을 수행합니다.
  3. 결과 비트를 Base62 알파벳으로 인코딩합니다.

이렇게 만든 난독화된 ID는 생성된 컬럼에 저장됩니다. 삽입 순서값은 01Y9I, 01Y9L, 그 다음 01Y9K와 같이 될 수 있으며, 알파벳 순서상 마지막 두 값은 뒤바뀝니다(01Y9I가 먼저, 그 다음 01Y9K, 01Y9L).


Footnotes

  1. RFC 4122 – A Universally Unique IDentifier (UUID) URN Namespace. 2

  2. gen_random_uuid() function introduced in PostgreSQL 13.

  3. GUID – Globally Unique Identifier (Microsoft terminology).

  4. PostgreSQL 18 planned release (Fall 2025).

  5. Hacker News discussion on UUID collision probability.

  6. “Short alphanumeric pseudo random identifiers in PostgreSQL” – detailed implementation guide.

Back to Blog

관련 글

더 보기 »

Single State 모델 아키텍처

문제 설명 현대 시스템 아키텍처는 종종 단순성과 일관성을 희생하면서 규모와 유연성을 우선시합니다. 마이크로서비스를 도입하려는 급박함 속에서…