UUID v4 구조와 중복 확률, 122비트의 뜻

f47ac10b-58cc-4372-a567-0e02b2c3d479 같은 문자열을 처음 보고 "이게 정말 안 겹치나" 싶었던 분을 위한 글입니다. 결론부터 말하면 UUID v4는 128비트 중 122비트가 무작위이고, 나머지 6비트는 "나는 v4다"를 알리는 고정 표식입니다.
그래서 안 겹친다는 말은 믿음이 아니라 계산입니다. 자리 구조만 알면 눈으로도 v4인지 가려낼 수 있고, 중복 확률도 직접 따져볼 수 있습니다.
요약 ① 36자 = 16진수 32자 + 하이픈 4개. 128비트(16바이트)를 8-4-4-4-12로 끊은 것입니다. ② 세 번째 덩어리의 첫 글자는 항상
4(버전), 네 번째 덩어리의 첫 글자는8·9·a·b중 하나(variant)입니다. ③ 남는 122비트가 무작위라 경우의 수는 2의 122제곱, 약 5.3 × 10³⁶ 가지입니다.
왜 하필 8-4-4-4-12로 끊겨 있을까
UUID는 128비트짜리 값입니다. 16바이트를 16진수로 적으면 한 바이트가 두 글자가 되니 32글자가 되고, 여기에 하이픈 4개를 끼워 넣어 다섯 덩어리로 끊습니다.
그래서 UUID 문자열의 길이는 언제나 36자입니다. 길이가 36이 아니면 표준 표기가 아니라고 바로 판정할 수 있습니다.
덩어리를 나누는 자리는 규격이 정한 고정값입니다. 2024년 5월에 나온 RFC 9562가 현행 규격이며, 오래 쓰이던 RFC 4122를 대체했습니다.
| 그룹 | 글자 수 | 하이픈 뺀 16진수 32자 기준 자리 | 특별한 자리 |
|---|---|---|---|
| 1번째 | 8자 | 1~8 | — |
| 2번째 | 4자 | 9~12 | — |
| 3번째 | 4자 | 13~16 | 첫 글자 = 버전 |
| 4번째 | 4자 | 17~20 | 첫 글자 = variant |
| 5번째 | 12자 | 21~32 | — |
RFC 9562는 버전 필드를 6번째 옥텟의 상위 4비트(전체에서 48~51번 비트)로 정의합니다. 이 자리가 세 번째 그룹의 첫 글자입니다.
v4는 그 4비트가 0100으로 고정되므로 화면에는 항상 4로 보입니다. 앞의 예시 ...-4372-...에서 맨 앞의 4가 바로 그것입니다.
variant 필드는 8번째 옥텟의 상위 2비트, 즉 네 번째 그룹의 첫 글자 자리입니다. 이 2비트가 10으로 고정되기 때문에 그 한 글자는 8·9·a·b 넷 중 하나만 나옵니다.
정리하면 v4의 겉모습은 이렇게 고정돼 있습니다. y 자리에는 8·9·a·b만 올 수 있습니다.
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
↑ ↑
버전 4 variant(8/9/a/b)
도구 없이 직접 확인하는 방법
받은 값이 진짜 v4인지 확인하는 방법은 세 가지입니다.
1. 눈으로 봅니다. 하이픈으로 끊었을 때 8-4-4-4-12가 맞는지, 세 번째 덩어리의 첫 글자가 4인지, 네 번째 덩어리의 첫 글자가 8·9·a·b 중 하나인지만 보면 됩니다.
2. 브라우저에서 직접 만들어 봅니다. 개발자 도구(대개 F12)의 콘솔에서 아래를 입력하면 v4가 하나 나옵니다.
crypto.randomUUID()
// → "36b8f84d-df4e-4d49-b662-bcde71a8764f"
이 함수는 암호학적으로 안전한 난수로 v4를 만듭니다. 다만 보안 컨텍스트(HTTPS 또는 localhost)에서만 동작하므로, http:// 로 열린 페이지에서는 함수가 없다고 나올 수 있습니다.
3. 정규식으로 형식을 검사합니다. 길이·구분 위치·버전·variant를 한 번에 봅니다.
const isV4 = (s) =>
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s);
isV4("f47ac10b-58cc-4372-a567-0e02b2c3d479") // true
isV4("f47ac10b-58cc-1372-a567-0e02b2c3d479") // false (v1)
번거로운 지점은 여기서 시작됩니다. 콘솔은 한 번에 한 개씩만 뽑히고, 여러 개가 필요하거나 하이픈을 뺀 형태·대문자 형태로 바꾸려면 그때마다 코드를 고쳐 다시 실행해야 합니다.
저희 도구로 확인하기. 개수를 넣고 생성을 누르면 목록이 한 번에 나오고, 하이픈 제거·대문자 옵션과 전체 복사 버튼이 함께 있습니다.
설치·회원가입 없이 브라우저 안에서 바로 생성되며, 만들어진 UUID는 서버로 전송되지 않습니다.

122비트가 무슨 뜻인가 — 중복 확률을 직접 계산해 보기
128비트에서 버전 4비트와 variant 2비트를 빼면 122비트가 남습니다. RFC 9562는 v4를 random_a 48비트, random_b 12비트, random_c 62비트로 나누어 정의하고, 이 셋을 합친 122비트가 무작위라고 명시합니다.
그러면 v4가 가질 수 있는 값의 가짓수는 2의 122제곱입니다. 풀어 쓰면 5,316,911,983,139,663,491,615,228,241,121,378,304, 대략 5.3 × 10³⁶ 입니다.
감이 잘 오지 않는 크기라 다른 방식으로 적어 보면, 조(1조 = 10¹²)를 세 번 곱한 것보다 큰 수입니다.
겹칠 확률은 생일 문제로 계산합니다
중요한 건 특정 값 하나와 같을 확률이 아니라 내가 만든 것들끼리 겹칠 확률입니다. 이건 생일 문제와 같은 계산이고, 근사식은 아래와 같습니다.
p ≈ n² ÷ (2 × N) — n은 만든 개수, N은 가짓수(2의 122제곱)
이 식에 숫자를 넣으면 다음과 같이 나옵니다. 직접 계산해 확인할 수 있는 값입니다.
| 만든 UUID 개수 | 하나라도 겹칠 확률(근사) |
|---|---|
| 10억 개 | 약 9.4 × 10⁻²⁰ (1000경분의 1보다 작음) |
| 약 103조 개 | 약 10억분의 1 |
이 근사식은 확률이 아주 작은 구간에서만 맞습니다. 확률이 절반이 되는 지점은 생일 문제의 다른 식 n ≈ 1.18 × √N 으로 구합니다.
여기에 N = 2의 122제곱을 넣으면 약 2.7 × 10¹⁸, 즉 271경 개가 나옵니다. 개별 서비스가 만드는 식별자 개수와는 규모가 다릅니다.
그래서 진짜 위험은 확률이 아니라 난수원입니다
위 계산은 122비트가 고르게 무작위일 때만 성립합니다. 난수가 편향돼 있거나 예측 가능하면 실제 가짓수가 줄어 계산 자체가 무의미해집니다.
RFC 9562가 암호학적으로 안전한 난수 생성기(CSPRNG)를 쓰라고 권고하는 이유가 이것입니다. 브라우저의 crypto.randomUUID() 와 crypto.getRandomValues() 가 여기에 해당합니다.
반대로 Math.random() 으로 자리를 채워 만든 UUID는 형식만 v4일 뿐 위 확률이 보장되지 않습니다. 규격도 공유 지식 체계 없이 전역 유일성을 보장하는 것은 불가능하다고 적어 두었습니다.
자주 막히는 지점
| 상황 | 사실 |
|---|---|
| 하이픈을 빼고 32자로 저장 | 자리 수가 고정이라 언제든 8-4-4-4-12로 되돌릴 수 있습니다 |
| 대문자로 표기 | RFC 9562의 ABNF는 대문자·소문자·혼합을 모두 허용합니다 |
| 중괄호로 감싼 표기 | GUID 표기 관례이며 RFC의 문자열 형식은 아닙니다 |
| 값이 전부 0 | Nil UUID이며 v4가 아닙니다(버전·variant 자리도 0) |
| 정렬해서 생성 순서 파악 | v4에는 시간 정보가 전혀 없어 불가능합니다 |
가장 자주 사고가 나는 건 대소문자입니다. 규격상 둘 다 유효한 같은 UUID이지만, 데이터베이스나 코드에서 문자열로 그대로 비교하면 서로 다른 값으로 취급됩니다. 저장 시점에 한쪽으로 통일해 두는 편이 안전합니다.
또 하나는 데이터베이스 인덱스입니다. RFC 9562는 v4처럼 시간순이 아닌 UUID가 데이터베이스 인덱스 지역성이 나쁘다고 직접 지적합니다. 연달아 만든 값이 인덱스상 흩어져 있어 삽입이 무작위 위치에서 일어나기 때문입니다.
이 문제를 겨냥해 RFC 9562가 새로 정의한 것이 v7입니다. 앞부분이 유닉스 에포크 기준 밀리초 타임스탬프라 생성 순서대로 정렬되고, 나머지 자리는 무작위로 채웁니다. 순서가 필요 없다면 v4로 충분합니다.
정리
- UUID는 128비트이고, 16진수 32자 + 하이픈 4개 = 36자를 8-4-4-4-12로 끊습니다.
- 세 번째 덩어리의 첫 글자가 버전입니다. v4면 항상
4입니다. - 네 번째 덩어리의 첫 글자가 variant입니다. 상위 2비트가
10으로 고정돼8·9·a·b만 나옵니다. - 남는 122비트가 무작위이며, 가짓수는 2의 122제곱(약 5.3 × 10³⁶)입니다.
- 10억 개를 만들어도 겹칠 확률은 1000경분의 1보다 작고, 50%가 되려면 약 271경 개가 필요합니다.
- 확률이 성립하려면 난수원이 암호학적으로 안전해야 합니다.
Math.random()기반 구현은 여기에 해당하지 않습니다.
설치·회원가입 없이 브라우저 안에서 바로 생성되며, 만들어진 UUID는 서버로 전송되지 않습니다.
자주 묻는 질문
UUID v4는 절대 겹치지 않는다고 보장되나요?
아니요, 규격도 그렇게 말하지 않습니다. RFC 9562는 공유 지식 체계 없이 전역 유일성을 보장하는 것은 불가능하다고 명시합니다. 대신 122비트 무작위라는 구조 덕분에 겹칠 확률이 실무에서 무시할 수 있는 수준으로 내려갑니다. 10억 개를 만들어도 하나라도 겹칠 확률은 1000경분의 1보다 작습니다.
세 번째 덩어리가 4로 시작하지 않는 UUID를 받았습니다
버전이 다른 UUID입니다. 그 자리는 UUID의 버전 번호를 그대로 보여 주는 자리입니다. 1 이면 시간과 노드 정보를 쓰는 v1, 3·5 는 이름을 해시해 만드는 버전, 7 이면 유닉스 밀리초 타임스탬프를 앞에 두는 v7입니다. 형식이 깨진 게 아니라 다른 방식으로 만든 UUID이므로, v4만 받아야 하는 곳이라면 정규식에서 걸러 내면 됩니다.
하이픈을 빼고 32자로 저장해도 되나요?
되돌릴 수 있습니다. 각 덩어리의 길이가 8-4-4-4-12로 고정돼 있어 하이픈을 다시 끼워 넣는 데 추가 정보가 필요 없습니다. 다만 시스템마다 표기가 섞이면 같은 값이 다른 문자열로 저장되므로, 하이픈 유무와 대소문자를 한 가지로 통일해 두는 것이 좋습니다. 도구의 하이픈 제거 옵션은 생성된 값에서 하이픈만 걷어 낸 형태를 보여 줍니다.
브라우저에서 만든 UUID가 서버로 전송되나요?
아니요. 이 도구는 브라우저에 내장된 crypto.randomUUID() 로 값을 만들며, 생성된 UUID는 서버로 전송되거나 저장되지 않습니다. 개수를 늘려 한 번에 여러 개를 뽑아도 마찬가지이며, 화면을 새로 고치면 목록은 사라집니다.
