명령어/DB

[PostgreSQL] UUID v4 vs v5 — 같은 입력에 같은 UUID를 재현한다

jykim23 2026. 7. 22. 23:15
반응형

설치·접속: PostgreSQL 설치와 접속

부제: 외부 시스템의 (namespace, 이름)을 UUID로 매핑하는데, 재실행해도 중복 없이 동일한 ID가 나와야 할 때

UUID 하면 보통 랜덤(v4)만 떠올린다. 코어의 gen_random_uuid()가 호출마다 다른 값을 준다.

SELECT gen_random_uuid() AS v4_a, gen_random_uuid() AS v4_b;
                 v4_a                 |                 v4_b
--------------------------------------+--------------------------------------
 635ed2ec-6eb8-4b37-9b73-2371f7bfeede | 6d145333-8f26-4a7d-9b2c-fa7e5a731090
(1 row)

새 행에 그냥 PK를 뿌릴 때는 이게 맞다. 문제는 "외부 주문 ext-42를 항상 같은 UUID로 부르고 싶다" 같은 결정론적 매핑이다. v4로는 매번 새 값이 나와 매핑 테이블 없이는 재현이 안 된다. v5는 namespace UUID와 이름 문자열을 SHA-1로 버무려 UUID를 만든다 — 같은 입력이면 언제나 같은 출력이다.

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

SELECT uuid_generate_v5(uuid_ns_url(), 'https://forcloud.tistory.com') AS call1;
SELECT uuid_generate_v5(uuid_ns_url(), 'https://forcloud.tistory.com') AS call2;
                call1
--------------------------------------
 4da6bac6-b141-53da-af4d-ce20f4ff3c60
(1 row)

                call2
--------------------------------------
 4da6bac6-b141-53da-af4d-ce20f4ff3c60
(1 row)

두 번 불러도 4da6bac6-...-c60으로 동일하다. (버전 니블이 5인 것도 보인다.) 이름이 하나만 달라져도 값이 완전히 갈린다.

SELECT uuid_generate_v5(uuid_ns_url(), 'https://example.com') AS other;
                other
--------------------------------------
 4fd35a71-71ef-5a55-a9d9-aa75c889a6d0
(1 row)

이 결정성 덕분에 매핑 테이블 없이 외부 자연키를 UUID로 안정적으로 접는다. 같은 소스 이름으로 두 번 INSERT해도 PK가 같으니 ON CONFLICT DO NOTHING으로 멱등(idempotent)하게 만든다.

CREATE TABLE map (id uuid primary key, source_name text);
INSERT INTO map(id, source_name)
VALUES (uuid_generate_v5(uuid_ns_url(), 'order:ext-42'), 'order:ext-42')
ON CONFLICT (id) DO NOTHING;
-- 같은 매핑을 한 번 더
INSERT INTO map(id, source_name)
VALUES (uuid_generate_v5(uuid_ns_url(), 'order:ext-42'), 'order:ext-42')
ON CONFLICT (id) DO NOTHING;
SELECT count(*) AS rows_after_two_inserts FROM map;
 rows_after_two_inserts
------------------------
                      1
(1 row)

두 번 넣었지만 행은 하나다. 함정: v5는 SHA-1 기반이라 암호학적 비밀값 파생용이 아니다(그건 pgcrypto). 그리고 이름이 같으면 결과가 같으므로, 충돌을 원치 않는 순수 유니크 ID에는 오히려 v4를 써야 한다. 결정성은 "재현"이 목적일 때만 장점이다.

이렇게도 쓴다

well-known namespace 상수 — URL/DNS/OID/X500. 같은 이름이라도 namespace가 다르면 다른 UUID가 나온다.

SELECT uuid_ns_url() AS ns_url, uuid_ns_dns() AS ns_dns;
-- ns_url = 6ba7b811-9dad-11d1-80b4-00c04fd430c8
-- ns_dns = 6ba7b810-9dad-11d1-80b4-00c04fd430c8

 

v4만 필요하면 확장 없이 코어 함수를 쓴다 — PK DEFAULT로 지정. (조합: DEFAULT)

CREATE TABLE evt (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), body text);

 

내 도메인 namespace를 하나 정해두고 그 아래로 이름을 매핑한다 — 조직 전용 결정론적 ID 체계.

-- 도메인 root를 DNS namespace로 v5 생성해 그걸 하위 매핑의 namespace로 재사용
SELECT uuid_generate_v5(uuid_ns_dns(), 'forcloud.tistory.com') AS my_namespace;

 

v3(MD5 기반)도 같은 결정론적 특성 — 해시 알고리즘만 다르다. 신규 설계는 보통 v5(SHA-1) 권장.

SELECT uuid_generate_v3(uuid_ns_url(), 'order:ext-42') AS v3;

시간 순 정렬이나 시퀀셜 삽입 지역성이 중요하면 v4/v5의 랜덤성이 인덱스 단편화를 부른다. 그럴 땐 시간 정렬형 UUID(v7 계열) 쪽을 검토한다. (경계)

반응형