콘텐츠로 이동

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/uuid v1.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/uuid v1.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 이 되면 재평가 (현실적 가능성 낮음)

References

  • ADR-0004 — PostgreSQL (uuid 타입 지원)
  • ADR-0005 — sqlc (uuid 타입 매핑)