테스트 및 QA · 읽는 시간 6분

2026년 UUID v4 vs v7: 비트 레이아웃, 정렬, 인덱스 비용

UUID v4와 v7을 비트 단위로 비교합니다: 122개의 무작위 비트 대 48비트 밀리초 타임스탬프, 정렬 동작, B-tree 인덱스 비용, 프라이버시, 인코딩 크기.

작성자: Charlie Simmon ·

UUID v4와 UUID v7은 모두 128비트 식별자이며, 유사점은 거기서 끝납니다. v4는 122비트를 무작위성에 사용합니다. v7은 앞부분에 48비트 Unix 밀리초 타임스탬프를 두고, 이어서 4개의 버전 비트, 74개의 무작위 비트, 2개의 변형(variant) 비트를 배치합니다. 버전 번호는 순위가 아닙니다. 식별자가 무엇을 노출할 수 있는지, 그리고 어떻게 정렬되는지를 나타내는 선언입니다.

두 버전 모두 2024년에 RFC 4122를 대체한 RFC 9562에 정의되어 있습니다. 실무적 선택은 세 가지 질문으로 귀결됩니다: 식별자가 생성 시간 기준으로 정렬되어야 하는가, B-tree 기본 키로 들어갈 것인가, 값을 가진 누구나 생성 시간을 읽을 수 있어도 괜찮은가. 이 가이드는 규칙 대신 바이트 레이아웃으로 이 질문들에 답합니다.

128비트 레이아웃, 나란히 비교

모든 UUID는 16바이트이며, 필드는 텍스트의 하이픈이 아니라 위치로 정의됩니다. 바이트 6의 상위 니블에는 버전이 들어갑니다. 바이트 8의 상위 두 비트에는 변형이 들어갑니다. 나머지는 모두 버전에 따라 달라집니다. 정규 문자열에서 13번째 16진수 문자가 버전이고, 17번째가 변형입니다.

v4의 경우 버전과 변형을 제외한 122비트가 무작위입니다. v7의 경우 처음 48비트가 빅엔디언 Unix 밀리초 타임스탬프이며, 그래서 v7 값은 원시 바이트로도 생성 순서대로 정렬됩니다. 나머지 74비트는 기본적으로 무작위이지만, RFC 9562는 그중 일부를 밀리초 내 단조성을 위한 카운터로 사용할 수 있도록 허용합니다.

필드비트v4v7
바이트 0-548무작위unix_ts_ms, 빅엔디언
바이트 6 상위 니블40100 (버전 4)0111 (버전 7)
바이트 6 하위 니블 및 바이트 712무작위rand_a
바이트 8 상위 비트210x 변형10x 변형
바이트 8-15 나머지62무작위rand_b
총 무작위 비트12212274

v4 자세히 보기

v4 UUID는 6개의 고정 비트를 가진 난수입니다. 바이트 6의 상위 니블은 0100이므로 정규 문자열의 13번째 문자는 항상 4입니다. 바이트 8의 상위 두 비트는 10이므로 17번째 문자는 항상 8, 9, a, b 중 하나입니다. 이것이 유일하게 예측 가능한 문자이며, 나머지 30개의 16진수 숫자는 무작위입니다.

무작위성이 곧 보안 속성입니다. v4 값에는 타임스탬프도, 시퀀스도, 머신 식별자도 없으므로 언제 어디서 생성되었는지 전혀 드러나지 않습니다. 따라서 열거될 수 없어야 하는 공개 식별자에 합리적인 선택이며, 단 암호학적으로 안전한 난수 소스로 생성되어야 합니다. 122비트 공간은 충돌을 무시할 수 있는 수준으로 만듭니다: 10억 개의 값에서 충돌 확률은 대략 2^-63입니다.

  • 버전 니블: 정규 문자열의 13번째 위치에 항상 4
  • 변형: 17번째 위치에 항상 8, 9, a, b 중 하나
  • 무작위 비트: CSPRNG에서 얻은 122비트
  • 의미 있는 기준으로 정렬되지 않음; 순서는 무작위
  • 생성 시간도 시퀀스도 노출하지 않음

v7 자세히 보기

v7 UUID는 시간 순서를 따릅니다. 바이트 0부터 5까지는 Unix 에포크 이후 밀리초 수를 빅엔디언으로 담습니다. 바이트 6의 상위 니블은 0111입니다. 바이트 6의 하위 니블과 바이트 7 전체가 rand_a를 이루고, 바이트 8의 상위 두 비트가 변형이며, 나머지 62비트가 rand_b입니다. 즉 74개의 무작위 비트와 검사만으로 읽을 수 있는 타임스탬프입니다.

타임스탬프는 48비트로, 10889년까지 밀리초를 표현하기에 충분하므로 오버플로는 실질적인 문제가 아닙니다. 같은 밀리초에 생성된 두 v7 값은 무작위 또는 카운터 비트에서만 차이가 나며, 시계가 뒤로 가지 않는 한 나중에 생성된 값은 항상 바이트 문자열로 비교했을 때 더 큽니다. 그 정렬이 바로 핵심입니다.

바이트내용예시
0-5Unix 밀리초, 빅엔디언0192f3c4a1b2
6 상위 니블버전 77
6 하위 니블 + 7rand_a, 12비트a1b
8 상위 2비트변형 108, 9, a, b 중 하나
8 (하위 6비트) ~ 15rand_b, 62비트무작위 또는 카운터

정렬과 밀리초 내 단조성

정규 텍스트의 사전순 정렬은 바이트 순서와 일치합니다. 하이픈이 고정된 위치에 있고, 소문자일 때 16진수 숫자가 그 값과 같은 순서로 정렬되기 때문입니다. 텍스트 형식으로 정렬하든 16바이트로 정렬하든 생성 순서를 얻습니다. 이는 조정 없이도 여러 머신에 걸쳐 성립하며, 그래서 v7이 이벤트 로그와 정렬 키의 일부가 되는 식별자에 매력적입니다.

문제는 단일 밀리초 내부입니다. 기본적으로 같은 밀리초에 생성된 두 값은 무작위 비트로 정렬되며, 이는 생성 순서가 아닙니다. RFC 9562는 rand_a 필드 또는 가장 왼쪽의 무작위 비트를, 각 타임스탬프 틱마다 무작위로 시드되는 카운터로 사용하는 방법을 설명합니다. 이 방법을 쓰면 실용적인 목적에서 밀리초 내 단조성이 확보되지만, 무작위성이 약간 줄고 동시 생산자가 신중히 다뤄야 하는 공유 카운터가 생깁니다.

  • 밀리초 간: 시계가 단조적이면 순서가 보장됨
  • 밀리초 내: 카운터를 쓰지 않으면 무작위 순서
  • 카운터 방식: 틱마다 무작위로 시드하고 값마다 증가
  • 시계 역행: 순서가 깨질 수 있으므로 단조 시계 소스를 사용
  • 동시성: 카운터를 원자적으로 증가시키지 않으면 속성을 잃음

B-tree에서의 인덱스 비용

무작위 v4 기본 키는 인덱스의 무작위 위치에 삽입됩니다. B-tree에서는 거의 모든 삽입마다 다른 리프 페이지를 건드리고, 버퍼 풀에서 페이지를 밀어내며, 페이지를 중간에서 분할해 절반만 채워진 상태로 남깁니다. 테이블이 커질수록 인덱스가 커지고 쓰기 증폭이 증가하며 캐시 적중률이 떨어집니다.

v7 키는 오른쪽 끝 근처에 삽입되므로 작업 집합이 작게 유지되고 페이지 분할이 저렴한 끝에서 일어납니다. 대가는 경합입니다: 모든 쓰기 작업이 같은 맨 오른쪽 페이지를 원합니다. 단일 데이터베이스에서는 초당 수백 건의 쓰기라면 보통 문제없지만, 동시성이 매우 높아지면 핫스팟이 되며, 일반적인 완화책은 배치 처리, 연결 수준의 인터리빙, 또는 상위 비트에 약간의 지터를 주는 것입니다.

속성무작위 v4 키시간 순서 v7 키
삽입 위치무작위 리프 페이지맨 오른쪽 리프 페이지
페이지 분할빈번, 페이지 중간드묾, 끝에서
버퍼 풀 지역성대형 인덱스에서 나쁨좋음
인덱스 크기더 큼, 낮은 채움률컴팩트
쓰기 핫스팟트리 전체에 분산오른쪽 끝에 집중
시간 기준 범위 스캔별도 컬럼 필요기본 지원

프라이버시와 타임스탬프 노출

v7 값은 그것을 가진 누구에게나 대략 언제 생성되었는지를 밀리초 단위로 알려주며, 따라서 그 시점 전후에 얼마나 많은 다른 레코드가 생성되었는지도 알려줍니다. 내부 키와 이벤트 ID에는 괜찮습니다. 가입량, 주문 빈도, 계정 나이를 드러내서는 안 되는 공개 식별자에는 적합하지 않습니다.

정렬과 불투명성이 모두 필요하다면, v7은 내부에 두고 별도의 무작위 공개 ID를 노출하거나, 시스템을 떠나는 모든 것에는 v4를 사용하는 것이 선택지입니다. 어느 버전이든 UUID를 능력 토큰으로 취급하지 마십시오. 둘 다 식별자이지 인가 수단이 아닙니다. v4 ID는 실무적으로 추측 불가능하지만, 그래도 모든 요청에서 접근 제어와 대조해 확인해야 합니다.

  • v7은 생성 시간을 밀리초 단위로 노출
  • 인접한 v7 값을 열거하면 레코드 볼륨이 드러남
  • v7은 내부에 두고 v4 또는 별도 공개 ID를 노출
  • 어느 버전도 접근 토큰이나 비밀이 아님
  • 무작위화된 v7은 존재하지 않음; 타임스탬프는 필수

저장 및 인코딩 크기

저장 크기의 답은 두 버전 모두 16바이트입니다. 정규 텍스트 형식은 36자이며, 그중 32자가 16진수 숫자이고 4자가 하이픈입니다. 텍스트 형식을 저장하면 크기가 두 배가 되고 비교가 느려지므로, 네이티브 UUID 타입이 있으면 그것을, 없으면 BINARY(16)을 사용하고 경계에서만 형식을 지정하십시오.

SQL Server에는 테스트해 볼 만한 특이점이 있습니다: uniqueidentifier 타입은 정규 문자열과 다른 바이트 순서로 정렬되며, 내부 바이트 순서가 RFC 4122와 다릅니다. v7 정렬에 의존하는 코드는 해당 플랫폼에서 정렬 동작을 가정하지 말고 검증해야 합니다. 컴팩트한 텍스트 형식이 필요하다면 패딩 없는 base64url이 22자를 제공하지만, 표준 UUID 표현이 아니므로 정규 형식을 기대하는 도구를 혼란스럽게 합니다.

표현크기비고
바이너리16바이트네이티브 저장, 가장 빠른 비교
정규 텍스트36자하이픈 포함 8-4-4-4-12
하이픈 없는 16진수32자일부 API와 로그에서 사용
패딩 없는 Base64url22자표준 UUID 형식 아님
Base3226자드묾, 일부 티켓팅 시스템에서 사용

선택, 마이그레이션, 테스트

식별자가 데이터베이스 키이거나, 별도 컬럼 없이 생성 시간으로 정렬하고 싶거나, 추가 위주 테이블을 만들 때는 v7을 선택하십시오. 값이 공개적으로 노출되거나, 생성 시간이 민감하거나, 시스템이 이미 타임스탬프 컬럼을 저장해 정렬에서 얻을 것이 없을 때는 v4를 선택하십시오. 둘 다 서로 다른 제약에 대한 올바른 선택이며, 한 테이블에서 둘을 섞는 것은 보통 실수입니다.

테스트는 값이 아니라 구조를 검증해야 합니다. 파싱 후 버전 니블, 변형 니블, 바이트 길이를 확인하십시오. v7의 경우 내장 타임스탬프가 테스트 윈도 내에 있는지, 순서대로 생성된 두 값이 올바르게 비교되는지 검증하십시오. 테스트에 리터럴 UUID를 스냅샷으로 저장하지 말고, 생성기가 카운터를 쓰지 않는 한 v7이 이전 값보다 엄격히 크다고 단정하지 마십시오. 카운터가 없으면 같은 밀리초 내 순서는 무작위입니다.

상황선호이유
대형 테이블의 기본 키v7지역성과 더 작은 인덱스
공개용 식별자v4타임스탬프 미노출
시간순 정렬 이벤트 로그v7컬럼 없이 기본 정렬
세션 또는 재설정 토큰둘 다 아님목적에 맞게 만든 무작위 토큰 사용
시간 컬럼이 있는 기존 테이블v4정렬이 이미 가능
시스템 간 교환둘 다둘 다 표준 128비트 UUID

질문

UUID v4와 v7은 크기가 같은가요?+
예. 둘 다 128비트, 16바이트이며 정규 텍스트로 36자입니다. 내부 레이아웃만 다릅니다.
UUID v7은 올바르게 정렬되나요?+
밀리초 간에는 예, 원시 바이트로도 정규 텍스트로도 그렇습니다. 단일 밀리초 내에서는 생성기가 카운터를 쓰지 않는 한 기본 순서가 무작위입니다.
데이터베이스 기본 키에는 어느 것이 더 좋나요?+
v7입니다. 삽입이 B-tree 전체에 흩어지지 않고 오른쪽 끝 근처에 머물기 때문입니다. 대가는 동시성이 매우 높을 때 마지막 페이지의 쓰기 핫스팟입니다.
UUID v7은 정보를 누출하나요?+
생성 시간을 밀리초 단위로 노출하며, 인접한 값은 레코드 볼륨을 드러냅니다. 내부에 두거나 시스템을 떠나는 식별자에는 v4를 사용하십시오.
UUID를 접근 토큰으로 쓸 수 있나요?+
아니요. UUID는 식별자입니다. v4 값조차 모든 요청에서 접근 제어와 대조해야 하며, 토큰은 자체 수명 규칙을 가진 목적에 맞게 만든 생성기에서 나와야 합니다.

예시는 합성 데이터입니다. 생성된 주소를 신원 확인, 계정 등록 또는 배송에 사용하지 마세요.

참조 표준

다음 글

모든 가이드