GN⁺: PostgreSQL와 UUID를 기본 키로 사용하는 것에 대해
(maciejwalkowiak.com)- UUID는 데이터베이스 테이블 기본 키로 자주 사용됨
- 생성하기 쉽고 분산 시스템 간에 공유하기 쉬우며 고유성을 보장
- UUID의 크기를 고려할 때 이것이 올바른 선택인지 의문이 들지만, 우리가 결정할 수 없는 경우가 많음
- 이 글은 "UUID가 키에 적합한 형식인가"에 초점을 맞추지 않고 PostgreSQL에서 UUID를 기본 키로 효율적으로 사용하는 방법에 대해 설명
PostgreSQL와 UUID를 기본 키로 사용하기
-
UUID란?
- UUID는 데이터베이스 테이블의 기본 키로 자주 사용됨
- 분산 시스템 간에 쉽게 공유 가능하며 고유성을 보장함
- UUID의 크기 때문에 적합한지 의문이 들 수 있지만, 선택의 여지가 없는 경우가 많음
PostgreSQL에서 UUID 데이터 타입
-
UUID를 문자열로 저장
- PostgreSQL은 문자열을 저장하기 위한
text
데이터 타입을 제공함 - 그러나
text
타입은 UUID를 저장하기에 적합하지 않음 - PostgreSQL은 UUID를 위한 전용 데이터 타입
uuid
를 제공함 -
uuid
타입은 128비트 데이터 타입으로, 하나의 값을 저장하는 데 16바이트가 필요함 -
text
타입은 1 또는 4바이트의 오버헤드가 추가됨
- PostgreSQL은 문자열을 저장하기 위한
-
실험 결과
- 두 개의 테이블을 생성하여 비교: 하나는
text
타입, 다른 하나는uuid
타입 - 10,000,000개의 행을 삽입한 후 테이블 크기와 인덱스 크기를 비교
-
text
타입을 사용하는 테이블은 54% 더 크고, 인덱스 크기는 85% 더 큼
- 두 개의 테이블을 생성하여 비교: 하나는
UUID와 B-Tree 인덱스
-
B-Tree 인덱스와 UUID
- 랜덤 UUID는 B-Tree 인덱스에 적합하지 않음
- B-Tree 인덱스는 순서가 있는 값과 잘 작동함
- Java의
UUID.randomUUID()
는 UUID v4를 반환하며, 이는 의사 랜덤 값임 - UUID v7은 시간 순서대로 정렬된 값을 생성하여 B-Tree 인덱스에 적합함
-
UUID v7 사용
- Java에서 UUID v7을 사용하려면
java-uuid-generator
라이브러리가 필요함 - UUID v7을 생성하면 삽입 성능이 향상될 수 있음
- Java에서 UUID v7을 사용하려면
UUID v7이 INSERT 성능에 미치는 영향
-
실험
- UUID v7을 사용하는 테이블을 생성하고, 10,000개의 행을 10번 삽입하여 성능을 측정
- 결과는 다소 무작위적이지만, UUID v7을 삽입하는 것이 약 2배 더 빠름
추가 읽을거리
- PostgreSQL 17에서 UUID v7이 네이티브로 지원될 가능성 있음
- UUID v7 형식에 대한 정보
- UUID가 데이터베이스 기본 키로서의 성능에 미치는 영향
요약
-
UUID의 길이 문제
- 최적화가 이루어져도 UUID는 기본 키로서 최적의 타입이 아님
- 선택의 여지가 있다면 TSID와 같은 다른 옵션을 고려할 것
-
최적화 필요성
- 대규모 데이터셋이나 높은 트래픽이 예상된다면 최적화를 고려해야 함
- 기본 키 변경은 어려운 작업이므로 처음부터 올바르게 설정하는 것이 중요함
-
주의사항
- 필자는 PostgreSQL 전문가가 아니며, 배운 내용을 공유하는 것임
- 유용했다면 댓글이나 트위터를 통해 피드백을 주길 바람
GN⁺의 정리
- 이 글은 PostgreSQL에서 UUID를 기본 키로 사용할 때의 효율적인 방법을 다룸
- UUID v7을 사용하면 삽입 성능이 향상될 수 있음을 실험을 통해 보여줌
- 대규모 데이터셋이나 높은 트래픽이 예상되는 경우 최적화가 필요함
- TSID와 같은 다른 옵션도 고려해볼 만함
Hacker News 의견
-
B-tree 친화적인 기본 키로 bigserial을 사용하고, 외부 레코드 로케이터 옵션으로 문자열로 인코딩된 UUID를 고려할 것을 권장함
- 비기술적인 사용자가 인용할 경우, PNR 스타일 로케이터와 같은 간단한 옵션을 먼저 고려할 것
- 서비스나 애플리케이션의 스키마 내에서 PK 유형을 혼합하지 말 것
- 고유 식별자로 UUIDv7을 사용할 때는 타임코드가 내재된 데이터에만 사용할 것
- hashids를 사용하지 말 것; 암호화 품질이 없고 일상적인 사람들에게 친숙하지 않음
- 인코딩 시 base64나 하이픈이 포함된 알파벳을 사용하지 말 것
-
데이터베이스 스키마 설계 시 관심사의 분리와 기계적 동조의 원칙을 염두에 둘 것
-
Stripe의 타이핑된 랜덤 ID는 실제로 랜덤이 아님
- 메타데이터, 포함된 타임스탬프, 샤드 및 참조 키, 버전 정보 등이 포함됨
- 개인적으로 base58로 인코딩된 AES 암호화 bigserial+HMAC 로케이터를 선호함
-
Postgres에서 랜덤 UUID는 큰 문제가 아님
- UUID(16바이트)는 serial(4바이트)이나 bigserial(8바이트)보다 크지만, 전체 테이블 수준에서는 큰 문제가 아님
-
Postgres에서 serial vs. random UUID vs. ordered UUID를 고려하기 전에 다른 많은 것들을 걱정해야 함
-
최근 Postgres PK로 ULID를 선택했으며, 이 기사에서 많은 도움을 받았음: https://brandur.org/nanoglyphs/026-ids
-
ULID를 선호하는 이유는 UUID 유형과 호환되며, 타임스탬프가 내장되어 있어 ID로 정렬하면 타임스탬프 순으로 정렬됨
-
비교에 'int64'도 포함되면 UUID와 전통적인 접근 방식의 오버헤드를 비교할 수 있어 좋을 것임
-
삽입 성능은 성능을 평가하는 나쁜 방법임
- B-Tree 성능은 삽입 시 더 좋지만, 대규모 트랜잭션에서는 어떨지 의문임
-
SQLite에서 UUID4가 선호되는 이유는 트랜잭션 잠금 동안 페이지 캐시 충돌 가능성이 적기 때문임
- Postgres 시스템에서도 비슷하게 적용될 수 있음
-
정수 자동 증가 기본 키를 선호함
- 이해하기 쉽고 정렬하기 간단함
- 대규모 배치 프로젝트에서 마지막 기본 키를 저장하고 그보다 큰 모든 것을 가져올 수 있음
-
UUIDv7 삽입 시간 벤치마크는 UUID 생성 시간을 포함함
- 단순히 인덱스 업데이트 비용을 분리해서 보고 싶음
-
PostgreSQL 17에서 UUIDv7 지원이 포함될 가능성이 낮음
- 최근 작업에서 커미터가 제거되었고, 버전 17은 이미 기능 동결 상태임
-
python-ulid를 사용하기 시작했으며, ULID가 UUID보다 우수함
-
UUID v7 표준 링크가 오래되었으므로, RFC 9562를 참조할 것: https://datatracker.ietf.org/doc/html/rfc9562