분산 고유 식별자 생성 전략(UUID·ULID·Snowflake ID)
1. 개요
가. 정의
분산 고유 식별자(Distributed Unique ID)란 여러 노드·서비스·데이터센터가 동시에 레코드를 생성하는 환경에서, 중앙 집중식 채번기에 의존하지 않고도 전역적으로 유일성(global uniqueness)을 보장하도록 설계된 식별자 생성 기법을 말한다. 대표적으로 난수 기반의 UUID, 시간 정렬이 가능한 ULID·UUIDv7, 비트 조합 기반의 Snowflake ID가 있다.
분산 고유 식별자는 "새로 만드는 데이터에 어떤 기본키(Primary Key)를 부여할 것인가"라는 데이터베이스 설계의 가장 기초적인 질문이, 단일 DB 시대를 지나 마이크로서비스·샤딩·멀티 리전 환경으로 넘어오면서 다시 어려운 문제로 부상한 데서 출발한다. 과거에는 RDBMS의 AUTO_INCREMENT나 시퀀스 오브젝트가 사실상의 표준이었지만, 수평 분할된 수십 개의 샤드와 지역 분산된 쓰기 노드가 동시에 삽입을 수행하는 오늘날에는 단일 채번 지점 자체가 병목이자 단일 장애점(SPOF)이 된다. 따라서 식별자 생성 전략은 단순한 구현 세부가 아니라 확장성·가용성·인덱스 성능·보안을 좌우하는 아키텍처 결정으로 다뤄진다.
나. 등장 배경과 필요성
가장 익숙한 채번 방식인 DB 자동 증가 키는 단일 노드 환경에서는 간결하고 인덱스 지역성도 뛰어나다. 그러나 샤딩을 도입하는 순간 치명적 한계가 드러난다. 샤드 A와 샤드 B가 각자 1부터 번호를 매기면 키가 충돌하고, 이를 피하려고 중앙 시퀀스 서버를 두면 모든 삽입이 한 지점을 거쳐야 하므로 처리량이 그 서버의 한계에 묶이고 네트워크 왕복 지연까지 더해진다. 멀티 리전으로 확장하면 지역 간 조정 비용이 폭증해 쓰기 지연이 수십 밀리초 단위로 커진다.
이 문제는 정보관리기술사 시험에서 "MSA·대규모 분산 시스템의 데이터 식별자 설계"나 "샤딩 환경의 기본키 전략"으로 변형되어 자주 다뤄지며, 단순 암기가 아니라 트레이드오프를 설명하는 논술이 요구된다. 따라서 각 방식의 장단점을 유일성 보장 메커니즘부터 풀어 설명할 수 있어야 한다.
또한 자동 증가 키는 예측 가능성이라는 보안 약점을 갖는다. /orders/1001 다음이 /orders/1002임이 자명하므로, 권한 검증이 허술하면 식별자를 순차적으로 바꿔 타인의 자원을 조회하는 IDOR(안전하지 않은 직접 객체 참조) 공격에 노출되고, 경쟁사가 "이번 달 주문 번호 - 지난달 주문 번호"로 영업 규모를 추정하는 열거(enumeration) 기반 정보 누출도 가능하다. 분산 고유 식별자는 이처럼 ① 조정 없는 유일성, ② 높은 생성 처리량, ③ (필요 시) 비예측성이라는 세 가지 요구를 동시에 충족하기 위해 등장했으며, 여기에 ④ 시간 정렬성(index locality)까지 더하는 것이 최신 설계의 핵심 과제다.
2. 식별자 생성 방식의 분류와 요구사항
분산 ID 설계는 "유일성을 어떻게 확보하는가"를 기준으로 크게 세 갈래로 나뉜다. 첫째는 난수/해시 기반으로, 충분히 큰 난수 공간을 써서 충돌 확률을 무시 가능한 수준으로 낮추는 UUIDv4가 대표적이다. 둘째는 시간 + 난수 결합으로, 상위 비트에 밀리초 타임스탬프를 두어 대략적 생성 순서로 정렬되게 하고 하위 비트는 난수로 채우는 ULID·UUIDv7이다. 셋째는 시간 + 노드 + 순번 조합으로, 각 노드에 고유 번호를 부여해 조정 없이 유일성을 얻는 Snowflake 계열이다.
flowchart TB
Root["분산 고유 식별자 생성 전략"]
Root --> A["난수/해시 기반(무조정)"]
Root --> B["시간+난수 결합(정렬 가능)"]
Root --> C["시간+노드+순번 조합(준조정)"]
A --> A1["UUIDv4(122bit 난수)"]
A --> A2["UUIDv1(시간+MAC)"]
B --> B1["ULID(48bit 시간+80bit 난수)"]
B --> B2["UUIDv7(RFC 9562)"]
C --> C1["Twitter Snowflake(64bit)"]
C --> C2["Instagram/Sonyflake 변형"]
위 분류에서 각 가지가 벌어지는 근본 이유는 유일성 보장 메커니즘의 차이다. 난수 기반은 "확률적으로 거의 겹치지 않는다"는 통계적 보장에, 조합 기반은 "노드 번호가 다르면 절대 겹치지 않는다"는 구조적 보장에 의존한다. 이 차이가 곧 노드 조정 필요성, 키 길이, 정렬 가능성의 차이로 이어진다.
여기서 "조정(coordination)"의 성격을 구분해 두면 설계 판단이 명확해진다. UUIDv4·ULID·UUIDv7은 생성 매 순간 어떤 노드와도 통신하지 않는 완전 무조정이라 오프라인·엣지·클라이언트 측 생성이 자유롭다. Snowflake는 평상시 생성에는 통신이 없지만 기동 시 워커 ID를 받는 단 한 번의 조정이 필요하다. 반면 DB 시퀀스·중앙 채번 서버는 매 발급마다 조정이 일어나는 방식이다. 조정 빈도가 낮을수록 가용성과 처리량은 좋아지지만, 그만큼 유일성을 다른 수단(난수 공간·노드 번호)으로 떠받쳐야 하므로 키가 길어지거나 비트 설계가 복잡해진다. 이 "조정 빈도 ↔ 키 복잡도"의 교환이 분산 ID 설계의 축을 이룬다.
좋은 분산 ID가 갖춰야 할 요구사항을 정리하면 다음과 같으며, 이들은 상호 트레이드오프 관계에 있어 한 방식이 모두를 만족하기는 어렵다.
| 요구사항 | 의미 | 특히 중요한 상황 |
|---|---|---|
| 전역 유일성 | 모든 노드·시간에 걸쳐 충돌 없음 | 샤딩·멀티 리전 쓰기 |
| 생성 처리량 | 초당 생성 가능한 ID 수 | 대량 주문·로그·메시지 |
| 시간 정렬성 | 생성 순서대로 대략 증가 | B-트리 인덱스 삽입 성능 |
| 비예측성 | 다음 값을 추측 불가 | 공개 URL·보안 식별자 |
| 조밀성·길이 | 저장·전송 비용 | 인덱스 크기·네트워크 |
3. UUID와 ULID — 무조정(coordination-free) 방식
가. UUID 버전별 특성
UUID(Universally Unique Identifier)는 128비트 값으로, 통상 32개 16진수와 4개 하이픈을 합친 36자 문자열로 표기한다. 2005년 RFC 4122로 표준화되었고 2024년 5월 RFC 9562가 이를 개정·대체하면서 새 버전을 추가했다. 가장 널리 쓰이는 UUIDv4는 버전·변형 식별용 6비트를 제외한 122비트를 난수로 채운다. 이 공간은 약 5.3×10³⁶개에 달해, 초당 10억 개씩 85년을 생성해도 한 번의 충돌이 발생할 확률이 극히 낮은 수준이다. 노드 간 어떤 통신도 필요 없으므로 조정 비용이 0이고, 오프라인 클라이언트에서도 즉시 생성할 수 있다는 점이 결정적 장점이다.
UUIDv4가 널리 쓰이는 또 다른 이유는 생태계의 성숙이다. 거의 모든 언어의 표준 라이브러리와 데이터베이스가 네이티브 UUID 타입과 생성 함수를 제공하므로 추가 인프라 없이 즉시 도입할 수 있고, 분산 추적(trace ID)·멱등성 키·이벤트 ID처럼 "짧은 수명에 조정이 불가능한" 식별자에 특히 잘 맞는다. 요컨대 성능이 극단적으로 중요한 대용량 기본키가 아니라면, v4는 여전히 가장 안전하고 무난한 기본 선택지다.
반면 UUIDv1은 100나노초 단위 타임스탬프와 네트워크 카드의 MAC 주소를 결합한다. 시간 정보가 들어가므로 이론상 정렬 가능성이 있지만, 비트 배치가 상위부터 시간 순이 아니어서 문자열 정렬로는 생성 순서가 드러나지 않으며, MAC 주소 노출로 생성 장비를 특정할 수 있다는 프라이버시 문제가 지적되어 왔다(과거 멜리사 바이러스 역추적 사례가 대표적이다). 이 때문에 공개 식별자로는 v4가, 추적 가능성이 필요 없는 내부 용도로는 신중히 v1이 쓰인다.
충돌 확률을 조금 더 구체적으로 보면, UUIDv4의 유일성은 생일 역설(birthday problem)로 가늠한다. 122비트 공간에서 충돌이 50% 확률로 처음 발생하려면 대략 2^61개, 즉 약 2.3×10¹⁸개를 생성해야 한다. 이는 전 세계가 수백 년간 폭발적으로 ID를 찍어내도 도달하기 어려운 규모라, 실무에서 UUIDv4 충돌은 "발생하지 않는다"고 간주하고 설계한다. 다만 이 보장은 난수원(entropy source)의 품질에 전적으로 의존하므로, 약한 의사난수 생성기나 가상화 환경 기동 직후 엔트로피 부족 상태에서 생성하면 실제 충돌 위험이 급등한다는 점은 반드시 유의해야 한다.
UUIDv4의 숨은 약점은 인덱스 지역성 부재다. 값이 완전 난수라 B-트리 인덱스에 삽입될 때 매번 트리의 무작위 위치에 끼어들어, 페이지 분할(page split)과 버퍼 캐시 미스를 유발한다. MySQL InnoDB처럼 클러스터형 인덱스에 기본키가 물리 정렬을 결정하는 엔진에서는 랜덤 UUID 기본키가 삽입 성능과 캐시 적중률을 크게 떨어뜨린다는 것이 실측으로 잘 알려져 있어, 대용량 테이블에서는 기피되거나 뒤에 설명할 정렬 가능 방식으로 대체된다. 수천만 건 규모 테이블에서 랜덤 UUID 기본키가 순차 키 대비 삽입 처리량을 수 배 떨어뜨렸다는 벤치마크가 다수 보고되어, 이 문제는 이론이 아니라 현장의 실질적 병목으로 다뤄진다.
나. ULID와 UUIDv7 — 시간 정렬 가능 ID
이 인덱스 문제를 겨냥해 등장한 것이 ULID(Universally Unique Lexicographically Sortable Identifier)다. ULID는 128비트를 상위 48비트 밀리초 타임스탬프 + 하위 80비트 난수로 구성하고, 이를 Crockford Base32로 인코딩해 26자 문자열로 표현한다. 상위에 시간이 오므로 문자열을 사전식으로 정렬하면 곧 생성 시간순이 되고, 같은 밀리초 안에서는 80비트 난수로 유일성을 확보한다. 그 결과 UUIDv4의 조정 없는 생성이라는 장점을 유지하면서도, 인덱스에 거의 단조 증가 형태로 삽입되어 페이지 분할이 급감한다.
RFC 9562가 새로 도입한 UUIDv7은 ULID와 사실상 같은 설계 철학을 표준 UUID 포맷 안에서 구현한 것으로, 상위 48비트에 유닉스 밀리초 타임스탬프를 두고 나머지를 난수로 채운다. 기존 UUID 저장·라이브러리 생태계를 그대로 쓰면서 시간 정렬성을 얻을 수 있어, 신규 설계에서는 v4의 기본 대안으로 빠르게 자리잡고 있다. 다만 시간 정보가 노출되므로 "언제 만들어졌는지"를 숨겨야 하는 보안 식별자에는 적합하지 않으며, 이 경우에는 여전히 완전 난수인 v4가 선택된다. 즉 정렬성과 비예측성은 동시에 극대화할 수 없는 트레이드오프이며, 용도에 따라 버전을 선택해야 한다.
ULID·UUIDv7 같은 시간 정렬 ID에도 주의할 함정이 있다. 같은 밀리초 안에서 생성된 ID들의 상호 순서는 하위 난수에 의해 결정되므로, 밀리초 미만의 생성 순서는 보장되지 않는다. 엄밀한 단조 증가가 필요하면 ULID의 "monotonic 모드"처럼 같은 밀리초 내에서는 난수 대신 직전 값에 1을 더하는 변형을 써야 한다. 또한 48비트 밀리초 타임스탬프는 약 8900년을 표현할 수 있어 수명 측면의 걱정은 사실상 없지만, 시간 정렬성이 클라이언트 시계에 의존한다는 점은 분산 생성 시 약한 고리가 된다. 서로 다른 기기에서 생성된 ID들의 전역 순서는 각 기기 시계의 정확도만큼만 신뢰할 수 있으므로, 정렬을 비즈니스 로직의 강한 전제로 삼아서는 안 된다.
4. Snowflake ID — 중앙 조정 기반 64비트 ID
가. 64비트 비트 구조
Snowflake ID는 2010년 트위터가 분산 환경에서 정렬 가능하면서도 64비트로 조밀한 식별자가 필요해 고안한 방식이다. 128비트 UUID는 길어서 인덱스·네트워크 비용이 크고, DB 시퀀스는 중앙 병목이라는 두 문제를 동시에 풀기 위해, 64비트 정수를 아래와 같이 네 영역으로 쪼갠다.
flowchart LR
S["부호 1bit(항상 0)"] --> T["타임스탬프 41bit(ms, 약 69년)"]
T --> W["워커 ID 10bit(노드 1024개)"]
W --> Q["시퀀스 12bit(ms당 4096개)"]
상위부터 부호 비트 1개(양수 보장용 0), 타임스탬프 41비트, 워커 ID 10비트, 시퀀스 12비트로 구성된다. 41비트 밀리초 타임스탬프는 기준 시점(epoch)으로부터 약 2^41ms, 즉 약 69.7년을 표현할 수 있어, 서비스 시작 시점을 커스텀 epoch으로 잡으면 수십 년을 커버한다. 워커 ID 10비트는 최대 1024개 노드를 구분하고, 시퀀스 12비트는 한 노드가 같은 밀리초 안에 최대 4096개(2^12)의 ID를 발급하게 한다. 결과적으로 이론상 전체 시스템은 밀리초당 1024×4096 ≈ 419만 개, 초당 약 42억 개의 ID를 조정 없이 생성할 수 있다.
나. 생성 절차와 장애 대비
생성 과정은 다음과 같이 동작한다. 노드는 현재 시각을 읽어 타임스탬프 필드를 채우고, 직전 발급과 같은 밀리초라면 시퀀스를 1 증가시키며, 시퀀스가 4096을 넘으면 다음 밀리초가 될 때까지 짧게 대기(busy-wait)한다. 밀리초가 바뀌면 시퀀스를 0으로 리셋한다. 상위 비트에 시간이 있으므로 생성된 ID는 전역적으로 대략 시간순 증가하며, 같은 노드 안에서는 완전히 단조 증가한다. 이 덕분에 ULID처럼 인덱스 지역성이 좋으면서도 64비트로 UUID의 절반 크기라는 이점을 갖는다.
Snowflake의 핵심 전제는 워커 ID의 유일성과 시계의 단조성이다. 두 노드가 같은 워커 ID를 쓰면 충돌하므로, 노드 기동 시 ZooKeeper·etcd·DB 등에서 중복 없는 워커 ID를 할당받아야 한다. 이 지점이 완전 무조정인 UUID와 달리 기동 시점의 경량 조정을 요구하는 부분이라 "준조정(semi-coordinated)" 방식으로 분류된다. 또한 NTP 시계 역행이나 윤초로 타임스탬프가 뒤로 가면 중복이 생길 수 있어, 실무 구현은 마지막 발급 시각보다 시계가 뒤처지면 ID 발급을 멈추거나 오차 범위 내에서 대기하는 방어 로직을 둔다.
다. 비트 배분의 유연성과 운영 환경
비트 배분 자체가 설계 파라미터라는 점도 중요하다. 타임스탬프·워커·시퀀스에 몇 비트를 줄지는 "수명 vs 노드 수 vs 초당 발급량"의 트레이드오프를 반영한 선택이며, 트위터의 10/12 배분은 "노드는 1024개면 충분하고 노드당 발급량을 최대화"한 균형점이다. 반대로 노드가 수만 개인 환경에서는 워커 비트를 늘리고 시퀀스 비트를 줄이거나 시간 해상도를 낮추는 식으로 재배분해야 한다. 즉 Snowflake는 단일 공식이 아니라 조직의 트래픽 프로파일에 맞춰 비트 폭을 조정하는 설계 틀로 이해하는 것이 정확하다. 쿠버네티스처럼 노드가 수시로 생겼다 사라지는 환경에서는 고정 워커 ID 할당이 어렵기 때문에, 워커 ID를 시퀀스 공간에서 동적으로 빌려오거나 포드 정보를 해시해 부여하는 변형, 혹은 중앙 세그먼트 할당기(예: Leaf-segment)와 결합하는 하이브리드 설계가 함께 검토된다.
5. 비교와 사례
가. 방식별 특성 비교
세 방식의 선택은 "무엇을 포기할 것인가"의 문제다. 아래 비교에서 보듯 완전 무조정과 조밀한 64비트 정렬성을 동시에 가질 수는 없다.
| 구분 | UUIDv4 | ULID / UUIDv7 | Snowflake |
|---|---|---|---|
| 길이 | 128bit / 36자 | 128bit / 26자(ULID) | 64bit |
| 유일성 보장 | 확률적(난수) | 확률적(시간+난수) | 구조적(노드+순번) |
| 정렬성 | 없음 | 시간순 정렬 | 시간순 정렬 |
| 노드 조정 | 불필요 | 불필요 | 기동 시 워커 ID 필요 |
| 비예측성 | 매우 높음 | 낮음(시간 노출) | 낮음(시간·노드 노출) |
| 시계 의존 | 없음 | 있음 | 강함(역행 방어 필요) |
나. 실무 적용 사례
실무 사례는 이 트레이드오프를 잘 보여준다. 첫째, 인스타그램은 초기 샤딩 환경에서 64비트 ID를 PostgreSQL 저장 프로시저로 생성했는데, 상위 41비트 시간, 중간 13비트 논리 샤드 ID, 하위 10비트 샤드별 시퀀스 조합으로 설계해 "ID만 보면 어느 샤드에 있는지 알 수 있는" 라우팅 이점까지 얻었다. 이는 Snowflake 사상을 샤드 식별에 응용한 대표 사례다. 둘째, 디스코드는 트위터 Snowflake를 채택하되 2015-01-01을 커스텀 epoch으로 삼았고, 메시지 ID의 시간 정렬성을 이용해 "특정 시각 이전/이후 메시지"를 별도 인덱스 없이 ID 범위 질의로 페이징한다. 셋째, 소니(Sonyflake)는 노드 수를 2^16개까지 늘리는 대신 시간 해상도를 10ms 단위로 낮춰 약 174년을 표현하도록 비트 배분을 재설계했는데, 이는 "노드가 아주 많고 초당 발급량은 상대적으로 적은" 환경에 맞춘 변형이다. 국내외 여러 서비스가 Baidu UidGenerator, Meituan Leaf처럼 Snowflake를 세그먼트 할당·시계 보정과 결합한 사내 라이브러리를 운영하는 것도 같은 맥락이다.
넷째, MongoDB의 ObjectId는 이 세 갈래의 설계 사상이 한 식별자에 녹아 있는 흥미로운 절충 사례다. 12바이트(96비트)로 구성되며, 상위 4바이트는 초 단위 유닉스 타임스탬프, 가운데 5바이트는 프로세스·머신을 구분하는 난수, 하위 3바이트는 증가 카운터다. 상위에 시간이 있어 대략적 시간 정렬이 되고(ULID·Snowflake의 장점), 가운데 난수로 노드 조정 없이 서로 다른 인스턴스 간 충돌을 피하며(UUID의 장점), 하위 카운터로 같은 초 안의 유일성을 보장한다. 128비트 UUID보다 짧고 64비트 Snowflake보다는 길지만 중앙 조정이 전혀 없다는 점에서, "완전 무조정 + 시간 정렬"을 96비트 안에서 실현한 실용적 설계로 널리 쓰인다. 이처럼 실제 시스템의 식별자는 대개 하나의 순수 유형이 아니라 여러 사상의 혼합임을 보여준다.
6. 심화 — 인덱스 지역성과 UUIDv7 표준화 동향
최근 식별자 설계의 가장 뜨거운 쟁점은 데이터베이스 인덱스 지역성이다. 쓰기가 많은 대용량 테이블에서 랜덤 UUIDv4 기본키는 B-트리의 무작위 지점에 삽입을 일으켜, 디스크 쓰기 증폭과 캐시 미스로 TPS를 눈에 띄게 떨어뜨린다. 반대로 단조 증가 키는 인덱스 오른쪽 끝에만 삽입되어 캐시 효율이 높지만, 다중 노드가 같은 핫 페이지에 몰려 락 경합(hotspot)을 유발할 수 있다. 그래서 설계의 균형점은 "완전 랜덤도, 완전 순차도 아닌, 시간 접두사 + 난수 꼬리"이며, 이것이 ULID와 UUIDv7이 주목받는 기술적 근거다.
이 쟁점을 둘러싸고 UUID·Snowflake 외에도 다양한 변형이 제안되어 왔다. KSUID는 32비트 초 단위 타임스탬프와 128비트 난수를 합쳐 160비트로 만들고 Base62로 인코딩해 URL 친화적이면서 시간 정렬이 되도록 했고, NanoID는 유일성보다 짧고 URL 안전한 문자열에 초점을 둔 난수 ID로 프런트엔드·단축 URL 영역에서 인기를 끈다. 이들은 모두 "시간 접두사 + 충분한 난수"라는 공통 골격을 공유하되, 길이·인코딩·정렬 보장 수준에서 서로 다른 지점을 택한 것이다. 즉 식별자 설계는 몇 개의 축(정렬성·길이·비예측성·조정 빈도) 위에서 좌표를 고르는 문제이며, 어느 라이브러리를 고르든 그 좌표가 자신의 요구에 맞는지를 먼저 따져야 한다.
운영 관점의 심화 쟁점으로 식별자 생성 자체의 관측성(observability)도 빼놓을 수 없다. Snowflake 노드의 시계가 역행해 ID 발급이 멈추거나, 특정 밀리초에 시퀀스 4096개가 소진돼 대기가 길어지면 그 자체가 지연 급증의 원인이 된다. 따라서 성숙한 운영 조직은 노드별 발급 QPS, 시퀀스 포화 횟수, 시계 역행 감지 이벤트, 워커 ID 할당 충돌을 지표로 수집해 조기 경보한다. 또한 커스텀 epoch이 41비트를 소진하는 시점(서비스 시작 후 약 69년)은 당장은 멀어 보여도 "언젠가 반드시 오는" 설계 부채이므로, 설계 문서에 소진 연도를 명시해 두는 것이 책임 있는 설계 태도다.
표준화 측면에서 2024년 5월 발표된 RFC 9562는 기존 RFC 4122를 대체하며 UUIDv6(v1 재배열로 정렬 가능)·v7(유닉스 시간 기반)·v8(커스텀)을 공식화했다. 이는 그동안 ULID·KSUID·Snowflake 등 비표준이 난립하던 "시간 정렬 가능 ID" 영역을 표준 UUID 체계로 흡수하려는 흐름으로, PostgreSQL·주요 ORM·언어 표준 라이브러리가 UUIDv7 지원을 빠르게 추가하고 있다. 기술사 관점에서는 "앞으로 신규 분산 시스템의 기본키 기본값이 v4에서 v7로 이동할 것인가, 그리고 64비트 조밀성이 중요한 영역에서는 Snowflake가 계속 공존할 것인가"가 핵심 전망이다. 아울러 공개 API의 식별자는 시간·노드 정보가 노출되지 않도록 v4를 쓰거나, 내부 Snowflake를 해시드(HMAC) 공개 토큰이나 Hashids로 한 번 더 감싸 열거 공격을 차단하는 설계가 보안 모범 사례로 자리잡고 있다.
7. 고려사항 및 시사점
기술사 관점에서 분산 고유 식별자 전략을 선택·도입할 때는 다음을 종합적으로 검토해야 한다.
- 용도별 다중 전략 채택: 하나의 방식이 모든 요구를 만족하지 못하므로, 내부 기본키는 인덱스 지역성이 좋은 Snowflake·UUIDv7로, 외부에 노출되는 공개 식별자는 비예측성이 높은 UUIDv4 또는 별도 토큰으로 분리 설계하는 것이 현실적이다. 하나의 엔티티가 내부 키와 공개 키를 함께 갖는 이중 식별자 패턴을 적극 고려한다.
- 인덱스·스토리지 비용 트레이드오프: 128비트 UUID는 64비트 Snowflake 대비 기본키·모든 외래키·인덱스에서 두 배의 공간을 쓰고 조인·네트워크 비용도 커진다. 레코드 수가 수십억 건에 이르는 테이블이라면 64비트 조밀성의 이점이 상당하므로, 유일성 보장 방식과 길이를 함께 저울질해야 한다.
- 시계 신뢰성과 장애 대비: Snowflake·ULID·UUIDv7은 모두 시스템 시계에 의존하므로, NTP 동기화 품질, 시계 역행 방어, 커스텀 epoch 고갈(41비트 소진) 시점을 운영 설계에 반영해야 한다. 워커 ID 할당 체계(ZooKeeper·etcd·DB)의 가용성도 ID 생성의 선행 조건임을 잊지 말아야 한다.
- 보안·프라이버시 영향 평가: 식별자에 담긴 시간·노드·MAC·샤드 정보는 그 자체로 메타데이터 누출이 될 수 있다. IDOR·열거 공격 방어를 위해 권한 검증을 식별자 비예측성에만 의존하지 않도록 하고, 필요한 경우 공개 식별자는 난수화·해시화한다.
- 마이그레이션 전략: 이미 자동 증가 키로 운영 중인 시스템을 분산 ID로 전환할 때는, 기존 키를 유지하면서 신규 식별자 컬럼을 병행 도입하고 참조를 점진적으로 이관하는 스트랭글러(strangler) 방식이 안전하다. 키 타입 변경은 모든 외래키와 애플리케이션 계약에 파급되므로 단계적 전환 계획이 필수다.
- 정렬·페이징 활용의 양면성: 시간 정렬 ID는 "ID 범위 질의"만으로 시계열 페이징을 구현하는 강력한 이점을 주지만, 이를 남용해 ID를 정렬·시간 비교의 유일한 근거로 삼으면 시계 오차·밀리초 내 순서 미보장의 함정에 걸린다. 생성 순서가 비즈니스적으로 중요하면 별도의 생성 시각 컬럼이나 논리적 시퀀스를 함께 두어 식별자와 순서 기준을 분리하는 편이 견고하다.
- 표준 수렴과 장기 호환성: RFC 9562로 시간 정렬 ID가 UUID 표준에 흡수되는 흐름을 고려하면, 신규 시스템은 사내 커스텀 포맷보다 표준 UUIDv7 채택이 생태계 호환성과 장기 유지보수 측면에서 유리할 수 있다. 다만 64비트 조밀성이 결정적인 영역에서는 Snowflake 계열이 계속 유효하므로, 표준 수렴을 맹목적으로 따르기보다 저장 비용·정렬성·보안 요구를 재평가해 선택한다.
참고자료
- RFC 9562, "Universally Unique IDentifiers (UUIDs)", IETF, 2024. https://www.rfc-editor.org/rfc/rfc9562
- Twitter Engineering, "Announcing Snowflake", 2010. https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake
- Instagram Engineering, "Sharding & IDs at Instagram". https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c
- ULID 사양(명세 저장소). https://github.com/ulid/spec
한 줄 요약: 분산 고유 식별자는 중앙 채번기 없이 전역 유일성을 보장하기 위한 설계로, 완전 무조정·비예측의 UUIDv4, 시간 정렬로 인덱스 지역성을 얻는 ULID·UUIDv7, 64비트 조밀성과 준조정 유일성을 제공하는 Snowflake가 대표적이며, 유일성 보장 방식·정렬성·길이·보안의 트레이드오프를 용도별로 분리 설계하는 것이 핵심이다.