PostgreSQL 18에 UUIDv7이 도입됩니다
PostgreSQL 18이 곧 출시될 예정이며, 현재 베타 테스트가 진행 중입니다. 이번 릴리스의 많은 개선사항 중에서도 UUIDv7 지원이 포함되었습니다. UUIDv7은 타임스탬프 기반의 UUID 변형으로 B-tree 인덱스와 잘 호환됩니다. 이 글에서는 UUID 전반에 대해 논의하고, UUIDv7이 왜 유용한지, 그리고 PostgreSQL에서 어떻게 사용할 수 있는지 알아보겠습니다.
TL;DR: PostgreSQL 18에서 도입되는 UUIDv7은 타임스탬프 기반으로 정렬 가능하고 B-tree 인덱스 성능이 우수한 UUID입니다.
uuidv7()
함수로 생성하며, 기존 UUID의 단점을 해결합니다.
PostgreSQL 18
PostgreSQL 18 베타 1이 며칠 전에 출시되었습니다. 이번 릴리스는 새로운 기능, 개선사항, 버그 수정으로 가득 차 있습니다. 평소와 같이 커뮤니티는 이를 시도해보고 문제를 보고하도록 권장하고 있으며, 9월에 고품질 릴리스를 출시하는 것이 목표입니다.
이번 릴리스의 주요 특징은 다음과 같습니다:
- 비동기 I/O (io_uring 사용) — 순차 스캔, 배큠에서 2-3배 속도 향상
- 다중 컬럼 B-tree 인덱스의 Skip scan + 더 스마트한 OR/IN 최적화
- 메이저 업그레이드 중 플래너 통계 유지
- UUIDv7 함수
- 가상 생성 컬럼
- OAuth 로그인 + md5 사용 중단 경고
- EXPLAIN ANALYZE에서 I/O, CPU, WAL 표시
- 시간적 제약조건, 비결정적 콜레이션의 LIKE, 케이스 폴딩
- 새로운 와이어 프로토콜 버전: 3.2 (2003년 이후 첫 번째!)
uuidv7()
이 가장 흥미로운 기능은 아니지만(그것은 비동기 I/O일 것입니다), 아마도 가장 기다려진 기능일 것입니다. PostgreSQL 17에 추가될 뻔했지만 포함되지 않아서 많은 사용자들이 실망했습니다. 저는 이 기능에 대해 너무 흥미로워서 베타 버전을 사용해보고 이에 대한 블로그 글을 쓰기로 결정했습니다.
UUID란 무엇이며 왜 유용한가요?
UUID는 트랜잭션부터 회사까지 다양한 항목의 식별자로 사용되는 128비트 값입니다. 공간과 시간에 걸쳐 고유하도록 설계되었으며, 중앙화된 서비스에 의존하지 않고도 높은 속도로 효율적으로 생성할 수 있습니다.
전통적으로 관계형 데이터베이스는 고유 식별자를 생성하기 위해 자동 증가 타입(SERIAL
또는 identity
와 같은)을 사용했습니다. 이는 단일 머신에서 효율적으로 수행할 수 있지만(이 경우에도 단점이 있습니다), 확장이 필요한 경우 모든 노드에서 고유한 식별자를 생성하는 방법이 필요합니다. Instagram 팀은 PostgreSQL 데이터베이스를 샤딩하면서 UUID로 마이그레이션한 과정에 대한 짧은 블로그를 작성했습니다.
UUID는 다음과 같은 일반적인 시나리오에서 데이터베이스의 기본 키로 유용합니다:
분산 데이터베이스에서 고유 ID 생성:
많은 분산 데이터베이스가 자동 증가(identity) 컬럼을 지원할 수 있지만, 제한사항과 성능 문제가 있습니다.추측할 수 없는 공개 식별자:
적절히 생성된 UUID는 추측하거나 예측할 수 없으며, 시스템에 대한 정보를 추론하는 데 사용할 수 없습니다. 예를 들어, 고객 식별자로 자동 증가를 사용하면 공격자가 모든 기존 식별자를 스캔하여 사용을 시도할 수 있고, 다음 식별자를 추측하여 고객 수를 추정할 수 있습니다.클라이언트가 식별자를 생성할 수 있도록 허용:
UUID를 사용하면 클라이언트가 서버와 조정하지 않고도 사용할 수 있는 식별자를 생성할 수 있습니다. 이는 서버와의 통신을 최소화하려는 모바일 앱과 서버리스 환경에서 유용합니다.
이러한 이점의 결과로 UUID는 많은 데이터베이스에서 기본 키로 사용됩니다. 하지만 데이터베이스에서 UUID 사용에 대한 3가지 우려사항도 있습니다:
- 정렬: UUID는 값으로 의미 있게 정렬할 수 없습니다.
- 인덱스 지역성: 새로운 UUID는 인덱스에서 서로 가깝지 않습니다. 즉, 삽입이 임의의 위치에서 수행됩니다. 이는 인덱스 팽창과 기타 성능 문제를 일으킬 수 있습니다.
- 크기: UUID는 128비트 값입니다. 대부분의 개발자는 기본 키로
INT
(32비트) 또는BIGINT
(64비트)를 기본적으로 사용합니다. 매우 작은 레코드가 많은 테이블의 경우 이는 의미 있는 오버헤드가 될 수 있습니다.
다음 섹션에서 설명하겠지만, UUIDv7은 이 3가지 우려사항 중 2가지를 해결합니다.
UUID의 크기는 디스크 공간이나 네트워크 대역폭이 제한적일 때 문제가 될 수 있지만, 최신 CPU는 단일 명령어(CMEQ
, SIMD 명령어의 일부)로 128비트 값을 비교할 수 있으므로 UUID에 대한 데이터베이스 작업이 고도로 최적화되어 있다는 점을 주목할 가치가 있습니다. 여기서 핵심은 데이터베이스와 애플리케이션 모두에서 UUID의 바이너리 표현(적절한 UUID 타입)을 사용하고 문자열 표현을 사용하지 않는 것입니다.
왜 UUIDv7인가요?
UUID는 2005년 RFC 4122에서 처음 표준화되었습니다. 이 RFC는 UUID의 5가지 변형을 정의하며, 그 중 변형 1과 4가 가장 일반적입니다. 이 사양은 나중에 2024년 5월에 발표된 RFC 9562에서 변형 6-8을 추가하도록 개정되었습니다(첫 번째 공개 작업 초안은 2020년에 발표되었지만). RFC 9562와 UUIDv7 생일 축하합니다!
사양 업데이트의 동기를 설명하기 위해 RFC 9562는 데이터베이스 키로 UUID를 사용하는 일반적인 사용 사례에 대해 논의합니다:
UUID가 인기를 얻은 한 영역은 데이터베이스 키입니다... 하지만 [RFC4122]에서 원래 정의된 UUID 버전 1-5는 다음과 같은 다른 바람직한 특성이 부족합니다:
UUIDv4(섹션 5.4에서 설명)와 같이 시간 순서가 아닌 UUID 버전은 데이터베이스 인덱스 지역성이 좋지 않습니다. 이는 연속적으로 생성된 새 값이 인덱스에서 서로 가깝지 않다는 것을 의미합니다. 따라서 임의의 위치에서 삽입을 수행해야 합니다. 이를 위해 사용되는 일반적인 구조(B-tree 및 그 변형)에 대한 결과적인 부정적인 성능 영향은 극적일 수 있습니다.
널리 분산된 많은 데이터베이스 애플리케이션과 대형 애플리케이션 공급업체는 데이터베이스 키로 사용할 더 나은 시간 기반의 정렬 가능한 고유 식별자를 만드는 문제를 해결하려고 했습니다. 이로 인해 지난 10년 이상 동안 약간씩 다른 방식으로 동일한 문제를 해결하는 수많은 구현이 생겨났습니다.
RFC는 계속해서 16개(!)의 서로 다른 비표준 UUID 구현을 명시하며, 각각 고유한 장단점을 가지고 있습니다. 여기에는 인기 있는 ULID
, Twitter의 Snowflake
, Instagram의 ShardId
등이 포함됩니다. 이 모든 구현은 새로운 사양을 설계할 때 평가되었습니다.
새로운 RFC가 3개의 새로운 UUID 변형을 명시하지만, 흥미로운 것은 UUIDv7뿐입니다. UUIDv6은 하위 호환성을 위해서만 도입되었습니다 - RFC는 "레거시 UUIDv1을 포함하지 않는 시스템은 대신 UUIDv7을 사용해야 합니다"라고 말합니다. UUIDv8은 실험적이고 공급업체별 확장을 위한 형식을 제공합니다.
UUIDv7은 정렬과 인덱스 지역성 우려사항을 모두 해결합니다. 가장 중요한 48비트로 Unix Epoch 타임스탬프를 사용하고, 나머지 74비트를 랜덤 값으로 유지합니다(추가 비트는 버전과 변형에 사용됩니다). 이로 인해 UUID가 시간 순서로 정렬 가능하고 고유해집니다. 표준은 또한 UUID에 밀리초 타임스탬프를 포함하거나 신중하게 시드된 카운터를 포함하여 단일 초 내에서 순서를 지원하는 옵션을 제공합니다(필요한 경우). 결과적으로 UUIDv7은 데이터베이스의 기본 키로 사용하기에 매우 적합합니다 - 고유성이 보장되고, 정렬 가능하며, 좋은 인덱스 지역성을 가집니다.
PostgreSQL 18의 UUIDv7
PostgreSQL 18 이전까지는 UUIDv7이 기본적으로 지원되지 않았습니다. 내장된 gen_random_uuid()
함수는 UUIDv4를 생성했고, 인기 있는 uuid-ossp
확장이 추가 UUID 변형에 대한 지원을 추가했지만 RFC 4122에서 명시된 변형으로 제한되었습니다.
PostgreSQL 18은 새로운 함수인 uuidv7()
을 추가하여 UUIDv7 값을 생성합니다. PostgreSQL 구현은 타임스탬프 바로 다음에 12비트 서브 밀리초 타임스탬프 분수를 포함합니다(표준에서 허용되지만 필수는 아님). 이는 동일한 PostgreSQL 세션(동일한 백엔드 프로세스)에서 생성된 모든 UUIDv7 값의 단조성을 보장합니다.
일관성을 위해 PostgreSQL 18은 명명을 맞추기 위해 gen_random_uuid()
의 별칭으로 uuidv4()
를 추가했습니다.
uuidv7()
을 호출하면 타임스탬프가 현재 시간인 새로운 UUIDv7 값이 생성됩니다. 다른 시간에 대한 UUIDv7 값을 생성해야 하는 경우 함수에 선택적 interval
을 전달할 수 있습니다.
UUID에서 타임스탬프와 버전을 추출하는 PostgreSQL의 기존 함수도 UUIDv7을 지원하도록 업데이트되었습니다. 다음은 새로운 함수를 사용하는 방법의 예입니다:
postgres=# select uuidv7();
uuidv7
--------------------------------------
0196ea4a-6f32-7fd0-a9d9-9c815a0750cd
(1row)
postgres=# select uuidv7(INTERVAL '1 day');
uuidv7
--------------------------------------
0196ef74-8d09-77b0-a84b-5301262f05ad
(1row)
postgres=# SELECT uuid_extract_version(uuidv4());
uuid_extract_version
----------------------
4
(1row)
postgres=# SELECT uuid_extract_version(uuidv7());
uuid_extract_version
----------------------
7
(1row)
postgres=# SELECT uuid_extract_timestamp(uuidv7());
uuid_extract_timestamp
----------------------------
2025-05-1920:50:40.381+00
(1row)
postgres=# SELECT uuid_extract_timestamp(uuidv7(INTERVAL '1 hour'));
uuid_extract_timestamp
----------------------------
2025-05-1921:50:59.388+00
(1row)
postgres=# SELECT uuid_extract_timestamp(uuidv7(INTERVAL '-1 day'));
uuid_extract_timestamp
----------------------------
2025-05-1820:51:15.774+00
(1row)
테이블에서 uuidv7()
을 기본 키로 사용하는 것은 간단하며, 타임스탬프를 추출하는 기능과 함께 UUID를 정렬 가능한 키로 사용하고 레코드의 생성 시간을 검사하기도 쉽게 만듭니다:
CREATETABLE test (
id uuid DEFAULT uuidv7()PRIMARYKEY,
name text
);
INSERTINTO test (name)VALUES('foo');
INSERTINTO test (name)VALUES('bar');
-- 다른 두 개보다 1시간 이전으로 만들어서 목록의 시작 부분으로 정렬됩니다
INSERTINTO test (id, name)VALUES(uuidv7(INTERVAL'-1 hour'),'oldest');
SELECT uuid_extract_timestamp(id), name FROM test ORDERBY id;
uuid_extract_timestamp | name
----------------------------+--------
2025-05-1919:55:43.87+00| oldest
2025-05-1920:55:01.304+00| foo
2025-05-1920:55:01.305+00| bar
(3rows)
이 모든 함수는 PostgreSQL 문서에 문서화되어 있으며, 구현 세부사항에 관심이 있다면 패치를 검토할 수 있습니다.
직접 사용해보세요!
PostgreSQL 18이 출시되면 평소와 같이 설치하여 uuidv7()
과 다른 모든 새로운 기능을 사용할 수 있습니다. 공식 릴리스는 9월에 계획되어 있지만, Beta 1
버전이 이미 사용 가능하며 커뮤니티는 사용자들이 이를 시도해보고 문제를 보고하도록 권장합니다.
베타 버전과 야간 스냅샷의 설치 지침은 여기에서 확인할 수 있습니다.
이전 버전에서 UUIDv7 사용하기: pg_uuidv7 확장
PostgreSQL 18을 기다릴 수 없다면, 이미 사용 가능한 서드파티 확장이 있습니다. pg_uuidv7은 PostgreSQL Extension Network(PGXN)에서 제공하는 확장으로, 이전 버전의 PostgreSQL에서도 UUIDv7 기능을 사용할 수 있게 해줍니다.
pg_uuidv7 확장의 특징
이 확장은 PostgreSQL 18의 네이티브 구현과 유사한 기능을 제공합니다:
-- UUIDv7 생성
SELECT uuid_generate_v7();
uuid_generate_v7
--------------------------------------
018570bb-4a7d-7c7e-8df4-6d47afd8c8fc
(1row)
-- 타임스탬프 추출
SELECT uuid_v7_to_timestamptz('018570bb-4a7d-7c7e-8df4-6d47afd8c8fc');
uuid_v7_to_timestamptz
----------------------------
2023-01-0204:26:40.637+00
(1row)
-- 타임스탬프를 UUIDv7로 변환
SELECT uuid_timestamptz_to_v7('2023-01-02 04:26:40.637+00');
uuid_timestamptz_to_v7
--------------------------------------
018570bb-4a7d-7630-a5c4-89b795024c5d
(1row)
성능
pg_uuidv7 확장의 uuid_generate_v7()
함수는 PostgreSQL의 네이티브 gen_random_uuid()
함수와 거의 동일한 성능을 제공합니다. 이는 프로덕션 환경에서도 안심하고 사용할 수 있음을 의미합니다.
설치 방법
x86_64 Linux 시스템에서는 다음과 같이 간단히 설치할 수 있습니다:
# 임시 디렉토리에서 작업
cd "$(mktemp -d)"
# 최신 릴리스 다운로드
curl -LO "https://github.com/fboulnois/pg_uuidv7/releases/download/v1.6.0/{pg_uuidv7.tar.gz,SHA256SUMS}"
# 압축 해제 및 검증
tar xf pg_uuidv7.tar.gz
sha256sum -c SHA256SUMS
# PostgreSQL 버전 확인 및 설치
PG_MAJOR=$(pg_config --version | sed 's/^.* \([0-9]\{1,\}\).*$/\1/')
cp "$PG_MAJOR/pg_uuidv7.so" "$(pg_config --pkglibdir)"
cp pg_uuidv7--1.6.sql pg_uuidv7.control "$(pg_config --sharedir)/extension"
# 확장 활성화
psql -c "CREATE EXTENSION pg_uuidv7;"
다른 아키텍처(Apple M1, Raspberry Pi 등)에서는 소스에서 직접 빌드해야 합니다.
언제 사용해야 할까요?
pg_uuidv7 확장은 다음과 같은 경우에 유용합니다:
- 현재 PostgreSQL 버전을 유지해야 하는 경우: PostgreSQL 18로 업그레이드할 수 없지만 UUIDv7의 이점을 누리고 싶을 때
- 즉시 UUIDv7을 사용하고 싶은 경우: PostgreSQL 18 출시를 기다리지 않고 지금 당장 UUIDv7을 사용하고 싶을 때
- 마이그레이션 준비: PostgreSQL 18로 업그레이드하기 전에 UUIDv7을 미리 테스트해보고 싶을 때
PostgreSQL 18이 출시되면 네이티브 함수로 마이그레이션할 수 있으며, 함수 이름만 약간 다를 뿐 기본적인 기능은 동일합니다.
마무리
PostgreSQL 18은 경험 많은 개발자들이 정말로 감사할 실용적인 개선사항을 제공합니다. UUIDv7에 대한 기본 지원은 조용하지만 영향력 있는 추가 기능으로, 데이터베이스 설계에서 오랫동안 지속된 문제점을 해결합니다.
UUID는 항상 트레이드오프였습니다: 안전하고, 고유성이 보장되며, 분산 시스템에서 효율적으로 생성할 수 있지만 B-tree 인덱스 사용 시 성능 단점이 있었습니다. UUIDv7은 두 세계의 장점을 모두 가져옵니다 — 전역적으로 고유하면서도 B-tree 인덱스와 쓰기 집약적 워크로드와 잘 호환되는 방식으로 정렬됩니다. PostgreSQL 18은 이를 훨씬 더 편리하게 사용할 수 있게 만듭니다.
기본 키에 UUID 사용을 주저했다면, 이제 그 결정을 재검토할 기회입니다. 베타를 시도해보고, 스키마에서 테스트해보고, 어떻게 작동하는지 확인해보세요. 멀티 테넌트 앱을 구축하든 단순히 더 안정적인 ID 생성을 원하든, UUIDv7은 살펴볼 가치가 있습니다.
PostgreSQL의 미래를 형성하는 가장 좋은 방법은 일찍 참여하는 것입니다 — 그러니 테스트 인스턴스를 실행하고 발견한 것을 커뮤니티에 알려주세요.
참고 자료: