본문으로 건너뛰기

1. 개요

  • 데이터베이스를 설계할 때 가장 신중하게 고려해야 할 요소 중 하나가 Primary Key의 선택입니다.
  • 이 선택은 단순히 레코드를 식별하는 것을 넘어서 시스템의 전반적인 성능과 확장성, 그리고 데이터의 무결성에 직접적인 영향을 미칩니다.

2. UUID(Universally Unique Identifier)

  • UUID는 RFC 4122 표준에 정의된 128비트 길이의 식별자로, 32개의 16진수를 5개 그룹으로 나누어 표현합니다.
UUID 구조

123e4567-e89b-12d3-a456-426655440000

  • 각 그룹은 하이픈(-)으로 구분됨
  • 총 36자 (32개 문자 + 4개 하이픈)

2.1 UUID의 장점

분산 시스템에서의 강점

  • 여러 서버에서 동시에 ID를 생성해도 충돌 걱정이 없습니다.
  • 데이터베이스 샤딩이나 마이크로서비스 아키텍처에서 특히 유용합니다.
  • ID 생성을 위한 중앙 서버가 필요 없어 시스템 복잡도가 낮아집니다.

향상된 보안성

  • ID의 무작위성으로 인해 다음 값 예측이 불가능합니다.
  • URL에 노출되어도 비즈니스 정보(가입 순서 등)가 유출되지 않습니다.
  • 자동 증가하는 ID를 통한 스크래핑이나 봇 공격을 방지할 수 있습니다.

유연한 생성 방식

  • 애플리케이션 레벨에서 ID 생성이 가능합니다.
  • 데이터베이스에 실제로 저장하기 전에 ID를 알 수 있어 관련 로직 처리가 용이합니다.
  • 벌크 인서트나 배치 처리 시에도 ID 충돌 걱정 없이 처리할 수 있습니다.

2.2 UUID의 단점

저장 공간과 성능

  • 128비트 크기로 인해 저장 공간이 더 필요합니다.
  • 인덱스 크기가 커져 메모리 사용량이 증가합니다.
  • B-tree 인덱스에서 랜덤한 값으로 인한 성능 저하가 발생할 수 있습니다.

가독성과 사용성

  • 긴 16진수 문자열로 인해 사람이 읽고 기억하기 어렵습니다.
  • 디버깅이나 로그 분석 시 불편할 수 있습니다.
  • API 응답이나 로그에서 차지하는 공간이 큽니다.

정렬 관련 제약

  • 기본적으로 생성 순서대로 정렬이 불가능합니다.
  • 최신 데이터 조회 같은 일반적인 쿼리의 성능이 저하될 수 있습니다.

3. Sequential ID

  • 순차적으로 증가하는 숫자를 사용하는 전통적인 Primary Key 방식입니다.

3.1 Sequential ID의 장점

최적화된 성능

  • B-tree 인덱스에서 순차적 삽입으로 인한 최적의 성능을 제공합니다.
  • 인덱스 단편화가 최소화됩니다.
  • 메모리 사용량이 UUID에 비해 절반 수준입니다.

우수한 가독성

  • 간단한 숫자 형태로 직관적입니다.
  • 데이터베이스 관계 파악이 용이합니다.
  • 로깅과 디버깅이 간편합니다.

효율적인 정렬과 페이징

  • 자연스러운 순서가 보장됩니다.
  • 최신 데이터 조회가 효율적입니다.
  • 페이지네이션 구현이 간단합니다.

3.2 Sequential ID의 단점

분산 환경에서의 한계

  • 여러 서버에서 동시에 ID를 생성할 때 충돌이 발생할 수 있습니다.
  • 중앙 집중식 ID 생성 서버가 필요할 수 있습니다.
  • 데이터베이스 샤딩 시 복잡도가 증가합니다.

보안 취약성

  • ID의 예측이 가능해 자동화된 공격에 취약합니다.
  • URL에서 비즈니스 정보가 노출될 수 있습니다.
  • 리소스 열거 공격이 가능합니다.

확장성 제한

  • BIGINT 타입(64비트)의 최대값 제한이 있습니다.
  • 대규모 데이터의 경우 한계에 도달할 수 있습니다.

4. UUID 버전별 특징 및 성능

4.1 UUID V1 (시간 기반)

img.png

  • 시간과 MAC 주소를 조합하여 생성하는 방식입니다.
  • 시간 정보가 세 부분으로 나뉘어 저장됩니다:
    • time_low: 타임스탬프의 하위 32비트
    • time_mid: 타임스탬프의 중간 16비트
    • time_hi: 타임스탬프의 상위 12비트
  • 정렬이 효과적이지 않은 이유:
    • 시간 정보가 "상위-중간-하위" 순서가 아닌 "하위-중간-상위" 순서로 배치됨
    • 이 필드는 시간의 가장 빠르게 변하는 부분(초, 밀리초 등)을 담고 있습니다
    • 마치 시계에서 "초:분:시" 순서로 표시하는 것과 비슷합니다

주요 특징

  • 60비트 타임스탬프 + MAC 주소 사용
  • 시간 정보가 포함되어 있어 생성 시점 추적 가능
  • 같은 시스템에서는 충돌이 거의 불가능
보안 주의

MAC 주소 포함으로 인해 생성 시스템의 정보가 노출될 수 있습니다.

4.2 UUID V4 (랜덤)

img_1.png

  • 가장 널리 사용되는 버전으로, 완전한 랜덤 값을 사용합니다.

주요 특징

  • 122비트의 랜덤 데이터 사용
  • 예측 불가능성이 가장 높음
  • 이론상 충돌 가능성 존재 (실제로는 무시할 수 있는 수준)
실무 팁

보안이 중요한 시스템에서는 V4 사용을 권장합니다.

4.3 UUID V6

img_2.png

  • UUID6은 UUID1의 단점을 개선하기 위해 만들어진 버전입니다.
  • UUID1과 동일한 필드 구조를 유지했습니다.
  • MAC 주소, 버전 정보 등 기본 구성 요소는 그대로 사용하되, 시간 정보를 개선했습니다.
  • 아래와 같이 V1의 시간 정보를 재배치 했습니다
    • 가장 중요한(높은 자리) 시간 비트가 앞에 옴
    • 그 다음 중요한 비트가 그 다음에 옴
    • 가장 덜 중요한(낮은 자리) 비트가 마지막에 옴
  • 따라서 연속으로 생성된 UUID가 시간 순서대로 정렬됩니다.
  • 그레고리력 에포크 사용 (1582년 기준)
  • 100-나노초 정밀도

4.4 UUID V7

img_3.png

  • UUID7은 새로운 시스템을 설계할 때 가장 권장되는 UUID 버전입니다.
  • 핵심적인 차이점은 타임스탬프 형식에 있습니다.
  • UUID1: 그레고리력 에포크 (1582년 10월 15일 기준)
    • 매우 오래된 시작점
    • 현대 시스템에는 불필요하게 긴 범위
    • 레거시 시스템과의 호환성 때문에 사용
  • UUID7: 유닉스 에포크 (1970년 1월 1일 기준)
    • 현대 컴퓨터 시스템의 사실상 표준
    • 대부분의 프로그래밍 언어와 데이터베이스가 사용
    • 실용적이고 효율적인 시간 범위

UUID7의 장점

  • 시간 순서대로 정렬 가능
  • 현대적인 시스템에 최적화
  • 불필요한 과거 시간 범위를 제거해 효율성 증가
실무 선택 가이드

새로운 시스템을 만들 때는 UUID7 선택 레거시 시스템과의 호환성이 필요한 경우만 UUID1 또는 UUID6 고려

4.5 UUID V6/V7의 시간 해상도 비교

  • UUID V6
    • 60비트 타임스탬프 사용
    • 100-나노초 단위의 정밀도
    • 최대 1초당 10,000,000개의 고유 타임스탬프 생성 가능
    • Clock sequence로 동일 타임스탬프 내 순서 보장
  • UUID V7
    • 48비트 Unix 타임스탬프 사용
    • 밀리초 단위의 정밀도
    • 최대 1초당 1,000개의 고유 타임스탬프 생성 가능
    • 동일 타임스탬프 내에서는 랜덤값으로 순서 결정
실무 선택 가이드

매우 높은 생성 속도가 필요한 경우 V6 사용 일반적인 애플리케이션은 V7로 충분 V7이 현대 시스템과의 호환성이 더 좋음

5. 성능 분석

5.1 핵심 요약

  • UUID V4는 랜덤값으로 인해 B-tree 인덱스 성능이 가장 낮음
  • UUID V1의 time_low 필드는 약 7분마다 롤오버되어 그 주기로 성능 변동
  • UUID V6/V7은 시간 정보가 올바른 순서로 정렬되어 Sequential ID와 비슷한 성능
  • PostgreSQL은 Non-Clustered 인덱스를 사용하여 UUID의 성능 패널티가 덜 심각
    • PostgreSQL이 UUID를 사용할 때 성능 저하가 InnoDB보다 훨씬 덜 심각하다
  • UUID를 char(36) 대신 binary(16)으로 저장하면 저장 공간과 인덱스 크기를 크게 줄일 수 있음

6. 선택 가이드

6.1 UUID를 선택해야 할 때

  • 분산 시스템을 구축하는 경우
  • 데이터베이스 샤딩을 계획하는 경우
  • 보안이 중요한 경우
  • ID 예측을 방지하고 싶은 경우

6.2 Sequential ID를 선택해야 할 때

  • 단일 데이터베이스 환경인 경우
  • 성능이 가장 중요한 경우
  • 데이터 양이 제한적인 경우
  • 정렬과 페이징이 빈번한 경우
실무 최적화 팁
  1. UUID를 사용할 때는 반드시 binary(16) 타입으로 저장하세요.
  2. 시간 기반 정렬이 필요하다면 UUID V6나 V7 사용을 고려하세요.
  3. PostgreSQL을 사용한다면 UUID의 성능 페널티가 덜 심각합니다.
  4. 하이브리드 접근도 고려해 보세요: 내부적으로는 Sequential ID를 사용하고, 외부 노출용으로는 UUID를 사용하는 방식입니다.