Postgres에서 UUID 버전 4 기본 키를 피해야 하는 이유

1 month ago 7

  • UUID v4는 무작위성이 높아 인덱스 비효율과 과도한 I/O를 유발하며, PostgreSQL에서 기본 키로 사용할 경우 성능 저하가 발생함
  • 무작위 삽입으로 인해 페이지 분할(page split)인덱스 단편화가 잦아지고, WAL 로그 크기 증가 및 쓰기 지연이 발생함
  • UUID는 16바이트 크기로 bigint보다 두 배의 공간을 차지하며, 캐시 적중률 저하와 메모리 낭비로 이어짐
  • 보안 식별자로 오해받지만, RFC 4122에 따르면 UUID는 추측 방지용 보안 수단이 아님
  • 새로운 데이터베이스에는 정수형 시퀀스 기반 키를 사용하고, 불가피할 경우 시간순 UUID v7을 사용하는 것이 권장됨

UUID v4의 성능 문제

  • PostgreSQL에서 UUID v4 기본 키를 사용한 데이터베이스는 지난 10년간 일관되게 성능 저하와 과도한 I/O를 보임
    • UUID v4는 122비트가 무작위로 생성되어 인덱스 정렬이 불가능함
    • 삽입 시 순차적 페이지에 저장되지 않아 랜덤 접근이 발생, 업데이트·삭제 시에도 비효율적 탐색이 필요함
  • B-Tree 인덱스는 정렬된 데이터를 전제로 하지만, UUID v4는 순서성이 없어 삽입 효율이 낮음
    • 각 삽입이 임의의 페이지에 기록되어 중간 페이지 분할이 자주 발생
    • 이로 인해 쓰기 지연(latency)WAL 증가가 발생함

UUID의 구조와 대안

  • UUID는 128비트(16바이트) 크기의 식별자이며, PostgreSQL에서는 binary uuid 타입으로 저장됨
  • UUID v4는 무작위 비트 기반, UUID v7은 앞 48비트에 타임스탬프를 포함해 인덱스 효율이 높음
  • PostgreSQL 18(2025년 예정)에서 UUID v7이 기본 지원 예정
  • UUID v7은 시간순 정렬이 가능해 페이지 밀도와 캐시 효율이 개선됨

UUID 선택 이유와 한계

  • 다중 클라이언트나 마이크로서비스 환경에서 충돌 없는 식별자 생성이 필요할 때 UUID가 사용됨
    • 예: 여러 데이터베이스 인스턴스에서 동시에 ID 생성
  • 그러나 RFC 4122는 “UUID는 추측이 어렵다고 가정하지 말라”고 명시, 보안 식별자로 부적합
  • 충돌 확률은 2.71×10¹⁸개 생성 시 50%로, 현실적으로 충돌 가능성은 낮지만 성능 비용이 큼

UUID의 공간 및 I/O 비효율

  • UUID는 bigint(8바이트) 의 두 배, int(4바이트) 의 네 배 공간을 차지
    • 대규모 테이블에서는 저장 공간 증가백업·복원 시간 증가로 이어짐
  • 인덱스 페이지 밀도 실험 결과
    • integer 인덱스: 97.64%
    • UUID v4 인덱스: 79.06%
    • UUID v7 인덱스: 90.09%
  • Cybertec 테스트에서 UUID v4 인덱스 조회 시 8.5백만 개 추가 페이지 접근, 31229% I/O 증가 확인
    • 동일 조건에서 bigint 인덱스는 27,332 버퍼 접근, UUID v4는 8,562,960 버퍼 접근

캐시 및 메모리 영향

  • UUID는 랜덤 분포로 인해 버퍼 캐시 적중률(cache hit ratio) 이 낮음
    • 더 많은 페이지를 캐시에 로드해야 하며, 필요한 페이지가 자주 퇴출(eviction)
  • 캐시 효율 저하로 쿼리 지연이 발생하며, 메모리 사용량 증가
  • 성능 유지를 위해 정기적인 인덱스 재구성(REINDEX CONCURRENTLY) 또는 pg_repack 사용 권장

성능 완화 방안

  • 메모리 확장: 데이터베이스 크기의 4배 RAM 확보 권장 (예: DB 25GB → 메모리 128GB)
  • work_mem 조정: 정렬 연산 시 더 많은 메모리 할당으로 성능 개선 가능
  • Rails 환경에서는 implicit_order_column 설정을 통해 UUID 대신 created_at 등 정렬 가능한 필드 사용
  • CLUSTER 명령으로 정렬 가능한 필드 기준으로 테이블 재배치 가능하나, 배타적 락 필요

정수형 키와 시퀀스 권장

  • 신규 데이터베이스에는 정수형 시퀀스 기반 키 사용 권장
    • integer(4바이트)는 약 20억 개, bigint(8바이트)는 훨씬 더 많은 고유 값 제공
  • 대부분의 비즈니스 앱은 integer로 충분하며, 대규모 서비스는 bigint 사용이 적합
  • UUID v4 대신 UUID v7 또는 sequential_uuids 확장을 사용하는 것이 현실적 대안

요약

  • UUID v4는 랜덤성으로 인한 인덱스 비효율, 높은 I/O, 낮은 캐시 효율을 초래
  • 보안 식별자로 사용할 수 없으며, 공간 낭비가 큼
  • 정수형 시퀀스 키가 대부분의 애플리케이션에 더 적합
  • 불가피하게 UUID를 사용해야 한다면 시간순 UUID v7을 선택해야 함
  • PostgreSQL에서 gen_random_uuid() 를 기본 키로 사용하는 것은 피해야 함

Read Entire Article