ID를 생성하는 중
1) 식별자에주의를 기울이는 이유
식별자 (ID) - 엔터티의 기본 키: 데이터베이스 줄, 메시지, 파일, 순서. 속성은 다음에 따라 다릅니다
독창성과 규모 (충돌, 수평 성장).
순서 및 정렬 (시간 상관, 복제, dedup).
스토리지 성능 (인덱스, 핫 페이지, 키 크기).
안전 (예측 불가능, 누출, 추측).
사용성/통합 (대소 문자에 민감하지 않은 짧은 IM 안전).
ID를 선택하는 것은 엔트로피, 주문 가능성, 길이, 생성 속도 및 착취 간의 절충입니다.
2) 주요 요구 사항 및 용어
독창성: 충돌 가능성은 허용 가능한 위험보다 낮아야합니다.
엔트로피: "임의의 양" 에는 ID (비트) 가 포함되어 있습니다.
시간 분류 가능/k-sortable-Lexicographic 정렬 시간 기반 정렬.
단조로운: 노드/스트림 내에서 감소하지 않는 시퀀스.
현지 입력: 새 인서트가 인덱스의 "꼬리" (핫 페이지의 위험) 에 얼마나 집중되어 있는지.
예측 가능성: 인접 ID (보안/API에 중요) 를 추측 할 수 있습니까?
표현: 바이너리/문자열, Base16/32/36/58/64, 하이픈, 케이스.
3) 주요 식별자 제품군
3. 1 UUID
v4 (랜덤): 122 비트의 엔트로피. 장애가 있고 안전과 단순성에 좋습니다. 마이너스: 무작위 분포로 인한 "혼란" 지수-그러나 하중을 균등하게 소산하고 "핫 페이지" 를 제거합니다.
v1 (시간 + MAC): 정렬하지만 MAC/시간 (개인 정보 보호) 을 전달합니다. 종종 피했다.
v7 (시간 순서): 밀리 초 시간 + 랜덤 파트. 데이터베이스에서 시간별 사전 정렬 및 압축이 양호하도록 설계하십시오. 타협: 색인의 "핫 테일" 이 나타납니다. 연마/접두사/증분으로 처리합니다.
팁
외부 API 및 느슨한 주문 요구 사항의 경우-v4.
이벤트/로그 데이터베이스 및 "정렬 된" 키 - v7.
3. 2 ULID (Crockford Base32)
128 비트: 48 비트 시간 (ms) + 80 비트의 임의성. 사전 친화적으로 사람 친화적 인 ('I, L, O, U' 없이) 시간별로 사전에 정렬되어 있습니다. 모노톤 변형이 있습니다 (동일한 시간 스탬프로 임의의 부분이 증가 함).
장점: 가독성, 주문 가능성, 이식성.
단점: 한 시점에서 매우 높은 빈도의 인서트가있는 "핫 테일" 입니다.
3. 3 KSUID
160 비트: 시대 + 128 비트의 임의성에 대한 32 비트의 시간 (초). 더 큰 시간 범위와 안정적인 정렬, ULID보다 짧은 문자열? (더 이상은 아니지만 자체 인코딩으로) 분산 된 로그 및 개체에 적합합니다.
3. 4 눈송이 모양 (k 정렬 가능한 플레이크 ID)
클래식 스키마 (사용자 정의):
[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]
속성: 노드에서 모노톤 성장, 준 글로벌 고유성, 짧은 (64 비트) 이진 표현.
위험: 시계 의존성 (시간 드리프트/회귀), 한 진드기의 시퀀스 소진, 영역/작업자 비트의 조정.
처리: "클럭 백", 예비 시퀀스, 시간 감지기, PTP/NTP 분야에 대한 보호.
3. 5 개의 DB 시퀀스 (SEQUENCE/IDENTITY)
하나의 DBMS/shard에서 가장 간단한 모노톤 세대.
장점: 짧고 빠르며 로컬 테이블에 편리합니다.
단점: 분산 클러스터에서 전 세계적으로 어려움; 예측 가능한 (공개 키로 안전하지 않음) 색인의 뜨거운 꼬리를 만듭니다.
3. 6 개의 콘텐츠 주소 ID (해시 컨텐츠)
콘텐츠 www-256/Blake3 → 안정적인 ID, 중복 제거, 무결성 검사, 캐싱.
장점: 결정론, 대체로부터의 보호.
단점: 값 비싼 생성 (CPU), 충돌은 실제 0, 시간 정렬, 길이입니다.
4) 충돌과 "생일 역설" (직관적)
'n' 세대에서 크기 'b' 비트의 랜덤 ID에 대한 충돌 확률은 대략 다음과 같습니다
p ≈ 1 - exp (-n (n-1 )/2/2 ^ b) ≈ n ^ 2/2 ^ (b + 1) (for small p)
예:
- UUIDv4 (122 비트) n = 10 ² 12 (조) → p ~ 1e-14 (무시할 수 있음).
- n = 10 ² 9의 64 비트 랜덤 → 이미 p ~ 0입니다. 027 (눈에 띄는 위험).
- 결론: 64 비트 랜덤은 종종 거대한 시스템에 충분하지 않습니다. 96/128 비트 사용.
5) 인덱스, 핫 페이지 및 스토리지
랜덤 키 (v4) 는 인덱스 트리에 인서트를 균등하게 분배합니다. → "꼬리" 는 없지만 캐시 위치는 더 나쁩니다.
시간 분류 (v7/ULID/Snowflake) 는 "꼬리에" 삽입되어 → 더 나은 위치 및 압축이지만 높은 병렬 기록 하에서 핫 페이지의 위험이 있습니다.
- 테넌트/지역별 접두사/샤딩 (시간 전에 1-2 바이트 추가);
- 인터리빙: 더 높은 비트에서 임의성의 일부;
- 배치 인서트, B- 트리의 충전 인자, 큰 로그의 경우 BRIN/클러스터링으로 자동 전환.
크기가 중요합니다
'UUI (16B)' vs 'BIGINT (8B) '/' INT8' 은 메모리/캐시를 저장합니다. Base32/58/64 행의 크기가 20-60% 증가합니다. 데이터베이스의 경우 바이너리를 저장하고 가장자리의 문자열로 직렬화하십시오.
6) 보안 및 개인 정보 보호
IBM/API에서 SEQUENCE/INT를 공개 ID로 사용하지 마십시오: 추측 가능한 → 리소스 열거.
외부 참조에 임의의 예측할 수없는 ID (v4/v7/ULID/KSID) 를 추가합니다.
PII를 ID로 인코딩하지 마십시오. 속성을 사용하려면 암호화/부호 (예: JWE/JWS) 를 사용하거나 불투명 한 토큰을 사용하십시오.
맵 안전 인코딩: Base32 Crockford, Base58 ('0OIl' 제외), Base64url.
7) 다중 임대, 접두사 및 라우팅
형식: '[TENANT _ PREFIX] - [ID]' 또는 바이너리: 'tenant _ id | | id'.
장점: 빠른 필터/테넌트 파티, N + 1 스캔으로부터 보호합니다.
단점: 높은 비트에서 엔트로피 밀도를 악화시킬 수 있습니다 → 분포 (접두사 해시) 를 고려하십시오.
해시 접미사 (2-3 바이트) 는 충돌을 줄이고 파편 라우팅을 돕습니다: 'shard = hash (id)% N'.
8) 선택을위한 실질적인 권장 사항
API, 공개 링크, 엄격한 순서없이 분산 서비스: UUIDv4, ULID/KSUI.
UUIDv7 또는 ULID (모노톤) 와 같이 시간별로 정렬하는 로그/이벤트/주문.
로컬 단조롭고 짧은 키: 눈송이와 같은 64 비트 (시간 규율 필요) 가있는 초고 대역폭.
아티팩트/빌드/블롭의 금고: 컨텐츠 주소 지정 가능 (Ś-256) 및 맨 위에는 사람 친화적 인 짧은 "쇼케이스" (Hashids/link) 가 있습니다.
하나의 데이터베이스에있는 로컬 테이블: 공개 링크 (마스킹) 를위한 SEQUENCE/IDENTITY + 외부 "래퍼".
9) 구현 및 예
9. 1 PostgreSQL
필요에 따라 'btree' 또는 'hash' 인덱스를 저장하십시오.
sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- или uuid_generate_v4()
created_at timestamptz NOT NULL DEFAULT now(),
tenant smallint NOT NULL
);
-- For time-sortable (UUIDv7) store binary (uuid), generation in the application.
-- If you want a cluster by time:
CREATE INDEX ON orders (created_at DESC);
순차적 핫 픽스: 시간 분류 ID의 경우 상위 비트에 "소금" 을 추가하거나 테넌트의 점수:
sql
CREATE TABLE orders_t1 PARTITION OF orders FOR VALUES IN (1);
CREATE TABLE orders_t2 PARTITION OF orders FOR VALUES IN (2);
9. 2 Redis (원자 카운터/모누 토니아)
bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20 (worker_id<<10) (seq & 1023)
9. 3 눈송이와 같은 생성기 (의사 코드)
pseudo const EPOCH = 1704067200000 # custom epoch (ms)
state: last_ms=0, seq=0, worker=7, region=3
next():
now = epoch_ms()
if now < last_ms: wait_until(last_ms) # защита от clock back if now == last_ms:
seq = (seq + 1) & ((1<<12)-1) # 12 бит if seq == 0: wait_next_ms()
else:
seq = 0 last_ms = now return (now-EPOCH)<<22 region<<17 worker<<12 seq
9. 응용 프로그램에서 4 ULID/UUID
이동
go
// ULID t:= time. Now(). UTC()
entropy:= ulid. Monotonic(rand. New(rand. NewSource(t. UnixNano())), 0)
id:= ulid. MustNew(ulid. Timestamp(t), entropy)
//UUID v7 (if there is a library)
id:= uuid. Must(uuid. NewV7())
노드. js
js import { ulid } from 'ulid';
import { v4 as uuidv4 } from 'uuid';
const id1 = ulid();
const id2 = uuidv4(); // v4
파이썬
python import uuid, time id_v4 = uuid. uuid4()
For v7, use a library (for example, uuid6/7 third-party packages)
10) 제거 및 표현
데이터베이스에서 이진 ('BYTEA', 'UUI') → 컴팩트하고 빠릅니다. 가장자리에서 다음으로 변환하십시오
Base32 Crockford (ULID): 케이스가 둔감하고 시각적으로 유사한 문자가 없습니다.
Base58: 사람이 읽을 수있는 토큰을 위해 Base32/64로 간단히 설정하십시오.
Base64url: 탭에서 짧지 만 '-' 및' _ '.
문자열을 비교할 때 복제물을 피하기 위해 케이스와 형식 (하이픈/없음) 을 안정화하십시오.
11) 테스트 플레이 북 및 관찰 가능성
충돌: 메트릭 'id _ collsion _ total' (0이어야 함),> 0에서 경고하십시오.
접두사 배포: 높은 바이트의 히스토그램-구매를 찾고 있습니다.
생성 속도: 'ids _ per _ sec', p99 생성기 대기 시간.
시계 왜곡 (눈송이 용): 오프셋 노드, "시계가 돌아갔습니다" 이벤트.
색인 꼬리: p95/p99 'INSERT' 대기 시간; 자물쇠/핫 페이지의 비율.
- 주입 "클럭 드리프트/백" → 발전기가 대기/전환 중인지 확인하십시오.
- '시퀀스' 오버플로 밀리 초 → 다음 _ ms 대기 체크.
- 질량 병렬 처리 → 지수에 자물쇠 폭풍이 있는지 여부.
12) 반 패턴
공개 ID: 추측, 누출. 내부 ID보다 공개 불투명 ID를 사용하십시오.
UUIDv1 (MAC/time) out: 프라이버시.
1 조 항목 당 64 비트 랜덤 ID: 충돌의 실제 위험.
HA가없는 글로벌 "중앙 발전기": SPOF 및 병목 현상.
클럭 백 보호가없는 시간 정렬 ID: 순서의 중복/회귀.
토론/마이그레이션에서 명시 적 버전/접두사 → 혼돈없이 다른 ID 형식을 혼합합니다.
레지스터/양식이 다른 문자열로 ID 저장 → 숨겨진 복제본.
13) 구현 점검표
- 도메인 요구 사항에 대해 선택된 형식 (v4/v7/ULID/KSUI/Snowflake/SEQ/hash).
- 정의 된 주문 요구 사항 (정렬 가능성이 필요한지 여부).
- 충돌 확률 (b 비트, n 세대) 이 추정되고 위험 임계 값이 설정됩니다.
- 인코딩은 설계되었습니다 (DB + 사람이 읽을 수있는 쇼케이스의 이진).
- 시간 분류-클럭 백 보호, 시퀀스 제한 및 NTP/PTP 분야.
- 공개 ID의 경우-예측 불가능 (랜덤/ULID/KSUI), PII 부재.
- 해시 (id)% N, 다중 테넌트 접두사를 생각했습니다.
- 관찰 가능성: 충돌, 분포, 대기 시간, 시계 왜곡 측정 항목.
- 시퀀스/유지/창 길이 오버플로 테스트 사례.
- 형식, 버전, 시대, 비트 맵 및 마이그레이션 계획 문서.
14) FAQ
Q: 마이크로 서비스에 "기본" 을 선택하려면 무엇입니까?
A: UUIDv7 또는 ULID: 시간 순서, 많은 엔트로피, 가장자리의 간단한 세대. 외부 API의 경우 ULID/UIDv4도 약입니다.
Q: 짧고 사람이 읽을 수있는 ID가 필요합니다.
A: ULID/KSUI 또는 Base58-128 비트 랜덤/임시 ID 인코딩. 길이와 충돌을 기억하십시오.
Q: "짧은 숫자" ID를 만들 수 있지만 안전합니까?
A: 예: 내부 SEQ를 저장하고 외부에 불투명 한 토큰 (랜덤 96-128 비트) 또는 해시드에 솔트 + 시그니처를 제공하십시오.
Q: SEQ에서 UUIDv7로 어떻게 마이그레이션합니까?
A: 두 개의 트랙 인 새 열 'id _ new' (UUI) 를 입력 한 다음 새 ID에 대한 참조를 게시 한 다음 DC/외부 키를 전환하고 이전 열쇠를 삭제하십시오.
Q: 왜 ULID 인서트가 "핫" 되었습니까?
A: 키를 엄격하게 증가시키는 것을 하나의 색인으로 삽입합니다. 파티션/테넌트, 고차 비트 혼합, 배치 인서트 사용.
15) 총계
좋은 ID는 문제에 대한 올바른 속성 세트입니다. 충분한 엔트로피, 예측 가능한 정렬 (필요한 경우), 안전한 홍보 및 건전한 지수 이용. 단순성과 분배를 위해 UUIDv4/ULID/UUIDv7/KSUI, 밀도가 높은 단조로운 눈송이 및 짧은 키 (시간 분야), 로컬 테이블 시퀀스, 아티팩트를위한 컨텐츠 해시를 선택하십시오. 관찰 가능성과 테스트를 정리하면 식별자는 놀라움의 원천이되지 않습니다.