ADR-0021: PK Type: UUID v7¶
Umbra 의 모든 도메인 entity PK 는 UUID v7 (RFC 9562) 을 사용한다. 앱 레이어에서 생성하여 INSERT 시 전달한다.
Status¶
Accepted
- Decided at — 2026-04-13
- Decided by — Pablo
Context¶
PK 타입 선택의 기준:
- 분산 생성 가능 (앱 레이어에서 생성 가능)
- 시간 정렬 가능 (B-Tree 인덱스 효율)
- PostgreSQL 네이티브 타입 지원
- 외부 노출 시 추측 불가능 (auto-increment ID 의 이슈 회피)
후보:
- BIGSERIAL (DB auto-increment)
- UUID v4 (랜덤)
- UUID v7 (시간 정렬)
- ULID (비공식 표준, 시간 정렬)
Decision¶
UUID v7 (RFC 9562, 2024 정식 표준) 을 채택한다.
- 생성 위치 — 앱 레이어 (Go 의
github.com/google/uuidv1.6+) - DB 타입 — PostgreSQL
uuid(16 bytes) - 표기 — hex with hyphenated (표준, 36자)
- 예외 —
events.outbox.id는 BIGSERIAL (순서/페이지네이션 효율)
선택 근거:
- 표준 준수 — RFC 9562 정식 표준 (2024 년 채택). ULID 는 비공식 de-facto 표준
- 시간 정렬 — 밀리초 단위 prefix 로 인덱스 삽입 효율 (v4 의 랜덤 대비 B-Tree 친화적)
- PostgreSQL 네이티브 지원 —
uuid타입 그대로 (ULID 는 BYTEA/TEXT 저장) - Go 라이브러리 준비 완료 —
google/uuidv1.6+ 이NewV7()제공 - 외부 추측 불가 — 랜덤 비트가 충분히 포함되어 BIGSERIAL 의 ID 노출 이슈 없음
Consequences¶
Positive¶
- PostgreSQL uuid 타입 네이티브 → 저장/인덱싱 효율
- 시간 정렬로 B-Tree 인덱스 재배치 최소화
- 분산 환경에서 앱 레이어 생성 가능 → DB round-trip 없이 엔티티 ID 확보
- 외부 API/URL 에 노출 가능
Negative¶
- 표기 길이 36자 (BIGSERIAL 대비 긴 URL)
- PostgreSQL 18 이전에는 내장
uuidv7()함수 없음 → 앱 레이어 생성 의존 - 일부 타사 도구에서 UUID v7 구분 지원 미흡 (대부분 v4 로 처리해도 동작)
Neutral¶
- customerKey 같은 외부 노출 키는
user_{uuid}prefix 로 의미 식별 - 도메인 외부에 노출 시 base64url encoding 으로 축약 가능 (선택적)
Alternatives considered¶
Alternative 1: BIGSERIAL¶
Pros
- DB 자동 생성, 간단
- 짧은 URL
Cons
- 외부 노출 시 ID 추측 공격 가능 (
/subscriptions/123다음이 124) - 분산 생성 불가 (DB 왕복 필요)
- 순서 공개로 비즈니스 정보 유출 (가입자 수 등)
Why rejected — 결제 SaaS 의 외부 노출 안전성에 부적합. 내부 테이블(outbox)에만 제한적 사용.
Alternative 2: UUID v4¶
Pros
- 완전 랜덤으로 추측 불가
- 생태계 가장 넓음
Cons
- 시간 정렬 없음 → B-Tree 인덱스 삽입 비효율
- 대량 INSERT 시 페이지 분할 빈번
Why rejected — 인덱스 효율이 v7 보다 명확히 낮음.
Alternative 3: ULID¶
Pros
- 시간 정렬 가능
- Crockford Base32 (26자, UUID 36자 대비 짧음)
Cons
- 비공식 표준
- PostgreSQL 네이티브 타입 없음 (BYTEA 또는 TEXT 로 저장)
- 외부 시스템(Toss, Discord)과 연동 시 표기 불일치
Why rejected — UUID v7 이 시간 정렬 이점은 동등하면서 표준 준수와 네이티브 저장 이점이 큼.
Compliance¶
- 모든 entity PK 컬럼은
UUID PRIMARY KEY타입 - Go 에서 PK 생성은
platform/uuid.New()헬퍼 사용 (google/uuid.NewV7()wrapper) - 수동으로
gen_random_uuid()(v4) 사용 금지 → golangci-lint 또는 DB 마이그레이션 리뷰에서 차단 - customerKey, orderId 등 외부 노출 키는 UUID 를 포함한 의미 있는 형식 (
user_{uuid},sub_{uuid}_{cycle}_r{retry}) events.outbox.id만 BIGSERIAL 예외 (순서 기반 처리)
Revisit triggers¶
- PostgreSQL 18 도입 후 내장
uuidv7()함수가 안정화되면 DB 생성 방식 재검토 (현재는 앱 생성 유지) - UUID 보다 훨씬 짧은 표기 필요성이 생기면 base64url encoding 도입
- 초대규모 쓰기 TPS 에서 v7 의 시간 prefix 가 hotspot 이 되면 재평가 (현실적 가능성 낮음)