사전 UUID
포맷

UUID

gabury1고친 사람 github-actions[bot]

UUID 는 각자 혼자 만들어도 서로 겹치지 않게 값을 붙이는 방법입니다. 번호를 내주는 중앙 장부에 물어보지 않고 필요한 쪽이 바로 하나를 지어냅니다. 128비트짜리 값입니다. 글자로는 f81d4fae-7dec-11d0-a765-00a0c91e6bf6 처럼 적습니다.

쉽고 빠른 이해

무슨 일을 하나 — 겹치지 않는 이름표를 혼자 만듭니다. 서버 열 대가 동시에 주문을 받아도 각 서버가 붙인 주문 번호가 서로 부딪히지 않습니다.

왜 필요한가 — 번호를 한 곳에서 받아 오면 값을 만들 때마다 그곳에 다녀와야 합니다. 그곳이 멈추면 새 데이터를 아예 못 만듭니다. 각자 만들면 다녀올 곳이 없어집니다.

어떻게 만드나

  1. 128비트 중 여섯 비트는 이 값을 어느 방법으로 만들었는지 적는 데 씁니다
  2. 남는 비트는 만드는 법에 따라 무작위 숫자로 채우거나 지금 시각으로 채웁니다
  3. 채운 128비트를 16진수 32글자로 바꾸고 다섯 덩이로 끊어 적습니다

대가 — 값이 커지고 뜻이 없습니다. 정수 번호보다 두 배 큽니다. 무작위로 만든 값은 순서가 없어 데이터베이스 인덱스가 새 값을 받을 때마다 여기저기 흩어집니다. 값을 만드는 곳이 하나뿐이면 정수 번호가 낫습니다.

상세

UUID 는 Universally Unique Identifier 의 줄임말입니다. 우리말로는 범용 고유 식별자입니다. 여러 곳에서 각자 값을 지어도 서로 겹치지 않는 것을 노린 식별자입니다. 주문을 받는 서버가 열 대이고 각 서버가 받자마자 주문 번호를 붙여야 할 때 쓰는 값이 이런 것입니다.

겹침을 피하는 방법은 둘 중 하나입니다. 하나는 아주 넓은 값 중에서 무작위로 하나를 뽑는 것이고, 다른 하나는 만든 시각과 만든 기계를 값 안에 섞어 서로 갈라 놓는 것입니다. 어느 쪽이든 남에게 묻지 않고 혼자 끝냅니다.

중앙 장부를 없애서 얻는 것

데이터베이스가 번호를 하나씩 올려 주는 방식은 그 데이터베이스 한 곳이 번호를 쥡니다. 값이 필요하면 먼저 그곳에 다녀와야 합니다. 그곳이 멈추면 새 데이터를 못 만듭니다. 한 곳이 멈추면 전체가 멈추는 단일 장애점입니다.

UUID 는 그 왕복을 없앱니다. 값을 쓰는 쪽이 직접 만들기 때문에 데이터를 저장하기 한참 전에 미리 값을 정해 둘 수 있습니다. 네트워크가 끊긴 휴대폰 앱이 먼저 값을 붙여 두고 나중에 서버에 올려도 번호가 부딪히지 않습니다.

128비트의 구성

128비트는 전부 무작위가 아닙니다. 여섯 비트는 이 값을 읽는 쪽에 규칙을 알려 줍니다. 나머지는 버전마다 다르게 채웁니다. 아래 표는 비트 자리마다 무엇이 들어가는지를 보입니다.

비트 구간 길이 담는 것
0~47 48비트 시각 또는 무작위 숫자(버전별)
48~51 4비트 버전 — 채우는 법의 번호
52~63 12비트 시각 또는 무작위 숫자(버전별)
64~65 2비트 변형 — UUID 규칙을 따른다는 표시
66~127 62비트 무작위 숫자

변형은 이 128비트를 UUID 규칙으로 읽으라는 표시입니다. 버전은 그 안에서 어느 방법으로 채웠는지의 번호입니다. 둘 다 값 안에 박혀 있어서 받은 쪽이 따로 묻지 않고도 읽는 법을 압니다.

글자로 적을 때는 16진수 32글자를 8-4-4-4-12 로 끊고 사이에 붙임표를 넣습니다. 이 두 칸도 글자로 드러납니다. 앞의 예 f81d4fae-7dec-11d0-a765-00a0c91e6bf6 에서 셋째 덩이의 첫 글자 1 이 버전이고, 넷째 덩이의 첫 글자 a 가 변형입니다.

비트 구간과 글자 덩이를 한 그림에 겹쳐 놓으면 이렇습니다.

packet-beta
0-31: "첫째 덩이 8글자 · 시각 또는 무작위"
32-47: "둘째 덩이 4글자"
48-51: "버전 4비트 — 셋째 덩이 첫 글자"
52-63: "셋째 덩이 나머지"
64-65: "변형 2비트 — 넷째 덩이 첫 글자"
66-79: "넷째 덩이 나머지"
80-127: "다섯째 덩이 12글자 · 무작위"

버전별로 채우는 값

버전은 남는 비트를 무엇으로 채울지를 정합니다. 무작위 숫자로 채우면 값에 아무 정보가 안 남습니다. 시각으로 채우면 만든 순서가 값에 남습니다.

버전 남는 비트를 채우는 것 언제 고르나
1 만든 시각 + 네트워크 카드마다 붙은 고유 주소 만든 기계와 시각을 값에 남길 때
3 · 5 이름을 해시 함수로 눌러 만든 값 같은 이름이면 늘 같은 UUID 가 나와야 할 때
4 무작위 숫자 122비트 아무 정보도 안 싣고 싶을 때. 가장 흔합니다
6 버전 1 의 시각을 큰 단위부터 놓도록 다시 늘어놓은 값 버전 1 을 쓰던 곳이 순서를 얻고 싶을 때
7 밀리초 단위 시각 + 무작위 숫자 새로 만드는 곳에서 순서와 무작위를 같이 원할 때
8 직접 정한 규칙 위 어느 것도 안 맞아 형식만 빌릴 때

여기서 이름은 웹 주소나 도메인 이름처럼 미리 정해 둔 문자열입니다.

버전 2 는 특정 분야 전용으로 따로 정해진 값이라 일반 용도로는 고를 일이 없습니다. 고를 일이 있는 것은 대개 4 와 7 둘입니다. 값에 아무것도 안 남기려면 4 를, 만든 순서가 값에 남아야 하면 7 을 고릅니다.

겹치지 않는다는 말의 뜻

버전 4 는 무작위로 채우는 비트가 122개입니다. 뽑을 수 있는 값이 2의 122제곱 가지입니다. 이 정도로 넓으면 두 번 뽑아 같은 값이 나오는 일을 실무에서는 없는 일로 칩니다.

다만 없는 일로 치는 것이지 못 나온다는 보장은 아닙니다. 값을 많이 뽑을수록 어딘가에서 두 개가 같아질 가능성이 오릅니다. 그 가능성이 생각보다 빨리 오른다는 것이 생일 문제입니다.

그래서 겹침이 절대 없어야 하는 곳은 UUID 하나에 기대지 않습니다. 같은 값이 두 번 들어오면 막는 고유 제약을 데이터베이스 쪽에 따로 겁니다.

무작위 숫자의 품질과 정보 노출

겹침보다 먼저 걸리는 것은 무작위 숫자의 품질입니다. 무작위 숫자를 만드는 부품, 즉 난수 생성기가 예측할 수 있는 값을 내면 같은 값이 두 번 나올 수 있습니다. 남이 다음 값을 알아맞힐 수도 있습니다. 비밀번호 재설정 링크처럼 남이 못 맞혀야 하는 값에 UUID 를 쓸 때 이 대목에서 사고가 납니다.

값 자체가 정보를 흘리기도 합니다. 버전 1 은 만든 시각과 기계 주소를 값 안에 담고 있어서, 받은 쪽이 그 UUID 만 보고 어느 기계가 언제 만들었는지 읽어 냅니다.

데이터베이스에 넣을 때 치르는 값

기본 키를 UUID 로 두면 두 가지를 냅니다. 크기와 순서입니다.

크기부터 봅니다. 128비트는 16바이트입니다. 흔히 쓰는 정수 번호는 8바이트입니다.

기본 키는 모든 인덱스에 같이 실려 다녀서 이 차이가 테이블 하나에서 끝나지 않습니다. 글자 그대로 36글자 문자열로 저장하면 더 커집니다.

순서는 버전이 갈라 놓습니다. 무작위로 만든 값은 정렬 순서가 만든 순서와 아무 관계가 없습니다. B-tree 인덱스는 키 순서대로 값을 끼워 넣으므로, 방금 만든 값 둘이 인덱스의 서로 먼 페이지에 떨어집니다. 시각으로 시작하는 버전 7 은 새 값이 늘 마지막 페이지에 붙습니다.

버전 4 와 버전 7 이 만든 값이 인덱스 페이지에 어떻게 떨어지는지를 그림으로 봅니다.

flowchart TD
    subgraph V4["버전 4 · 새 값이 흩어진다"]
        N1["새 값"] --> P1["페이지 1"]
        P2["페이지 2"]
        N2["다음 값"] --> P3["페이지 3"]
    end
    subgraph V7["버전 7 · 새 값이 뒤에 붙는다"]
        Q1["페이지 1"]
        Q2["페이지 2"]
        M1["새 값"] --> Q3["페이지 3"]
        M2["다음 값"] --> Q3
    end

흩어지면 새 값을 쓸 때마다 다른 페이지를 디스크에서 읽어 올려야 합니다. 자주 쓰는 페이지만 메모리에 두는 이점도 줄어듭니다. 뒤에 붙으면 마지막 페이지 하나만 메모리에 있으면 됩니다.

이걸 안 고르는 때

값을 만드는 곳이 하나뿐이면 UUID 가 주는 이점이 없습니다. 번호를 하나씩 올려 주는 방식이 더 작습니다. 순서도 그냥 얻습니다.

사람이 읽거나 옮겨 적어야 하는 번호에도 안 맞습니다. 36글자를 전화로 불러 줄 수 없습니다. 주문 번호처럼 고객이 보는 값은 짧은 번호를 따로 두고 UUID 는 안쪽에서만 씁니다.

64비트 안에 순서까지 담아야 하는 곳은 다른 방식으로 갑니다. 스노우플레이크가 그 경우입니다. 시각과 기계 번호와 순번을 64비트에 나눠 담습니다.

고르는 순서를 간추리면 이렇습니다.

flowchart TD
    A{"값을 만드는 곳이 여럿인가"} -->|아니오| B["하나씩 올려 주는 번호"]
    A -->|예| C{"64비트 안에 담아야 하나"}
    C -->|예| D["스노우플레이크"]
    C -->|아니오| E{"만든 순서가 값에 남아야 하나"}
    E -->|예| F["버전 7"]
    E -->|아니오| G["버전 4"]

관련 항목

UUID 를 대신할 수 있는 다른 식별자

스노우플레이크 · ULID · 자동 증가 · 시퀀스 · 일련번호 · ObjectId · 티켓 서버 · 주키퍼 순차 노드

UUID 가 열로 들어가는 데이터베이스 구조

기본 키 · 대리 키 · 자연 키 · 복합 키 · 인덱스 · B-tree · 클러스터형 인덱스 · 페이지 분할 · 샤딩

UUID 를 채울 때 쓰는 재료

난수 생성기 · 엔트로피 · 해시 함수 · MD5 · SHA-1 · 타임스탬프 · 에포크 · MAC 주소

UUID 를 글자로 적어 나르는 표기

16진수 · Base64 · URI · JWT · 빅 엔디언 · 가변 길이 정수

UUID 를 고를 때 따지는 성질

생일 문제 · 멱등성 · 단일 장애점 · 분산 시스템 · 정보 노출 · 열거 공격

UUID 가 이름표로 붙는 대상

식별자 · 세션 식별자 · 액세스 토큰 · 캐시 키 · URL 단축기 · 트레이스 ID

다른 이름: Universally Unique Identifier · 범용 고유 식별자 · GUID