UUID 생성기
v4는 122비트 난수입니다. 예측할 수 없어 ID가 아무것도 드러내지 않아야 할 때 적합합니다. 순서가 무작위라 데이터베이스 기본 키로는 부적합합니다.
입력한 내용은 저장, 기록, 공유되지 않습니다.
브라우저에서 UUID를 생성합니다. 무작위 v4와 시간순 정렬이 가능한 v7 중에서 선택하고, 필요한 개수만큼 만들어 개별 또는 한 번에 복사할 수 있습니다.
사용 방법
- 무작위가 필요하면 v4, 시간순 정렬이 필요하면 v7을 선택합니다.
- 생성할 개수를 선택합니다.
- 개별로 복사하거나 모두 복사를 사용합니다.
128비트에 담긴 것
UUID는 16바이트이며, 익숙한 8-4-4-4-12 묶음으로 32자리의 16진수로 씁니다. 모든 비트를 자유롭게 쓸 수 있는 것은 아닙니다. 4비트는 버전을, 2~3비트는 변형(variant)을 나타냅니다. 그래서 여기서 만든 모든 UUID의 13번째 자리는 4나 7이고, 17번째 자리는 항상 8, 9, a, b 중 하나입니다.
버전 4 UUID는 남은 122비트를 무작위로 채웁니다. 버전 7은 앞 48비트를 밀리초 타임스탬프에 쓰고 나머지 74비트를 무작위로 채워, 예측 불가능성을 조금 내주는 대신 순서를 얻습니다.
하이픈에는 정보가 없고 대소문자에도 없습니다. 다만 RFC 9562는 출력은 소문자로, 입력은 둘 다 받아들이도록 정합니다. urn:uuid: 접두사나 Windows 도구가 좋아하는 중괄호는 표현 방식일 뿐 내용이 아닙니다.
v7이 존재하는 이유
버전 4 UUID는 균일하게 무작위입니다. 아무것도 드러내지 않아야 하는 식별자에는 딱 알맞지만, 기본 키로는 딱 원하지 않는 성질입니다.
B-트리 인덱스는 항목을 정렬된 순서로 보관합니다. 무작위 키를 넣으면 그 순서의 아무 곳에나 들어가므로 삽입할 때마다 다른 페이지를 건드리게 되고, 자주 쓰는 페이지 집합이 메모리를 넘어서면 모든 삽입이 쓰기뿐 아니라 읽기까지 동반합니다. 페이지는 고르지 않게 차서 분할되고, 인덱스가 대상 데이터보다 커지며, 테이블이 커질수록 쓰기 처리량이 떨어집니다. 순차적인 키라면 삽입이 끝에만 들어가고 뜨거운 페이지는 하나뿐이어서 이런 일이 생기지 않습니다.
버전 7은 상위 비트에 타임스탬프를 넣어 나중에 만든 ID가 먼저 만든 ID보다 뒤에 정렬되게 합니다. 전역 고유성을 유지하고 머신 간 조율도 필요 없으면서 그 지역성을 되찾는 것입니다. RFC 9562가 v7을 추가한 이유이며, 이제는 키 열의 기본값으로 알맞은 선택입니다.
대가는 분명히 있습니다. v7 UUID는 행이 대략 언제 만들어졌는지 드러내고, 연속된 ID는 순서와 대략적인 생성 속도를 드러냅니다. 식별자가 공개적으로 노출되고 둘 중 하나라도 민감한 정보라면 여전히 v4가 올바른 선택입니다.
충돌
두 버전 4 UUID가 충돌하려면 122개의 무작위 비트가 반복되어야 합니다. 이 숫자를 실감하게 하는 흔한 방법은 생일 문제 계산입니다. 초당 약 10억 개의 UUID를 약 85년 동안 만들어야 전체 집합 어딘가에서 충돌이 한 번이라도 일어날 확률이 50%에 이릅니다.
그러니 실제로는 일어나지 않습니다. 현실적인 실패 원인은 확률 문제가 아닙니다. 같은 스냅숏에서 복제한 가상 머신이나 부팅 시 엔트로피가 없는 임베디드 장치처럼 난수 생성기의 시드가 잘못된 경우, 열 크기에 맞추려고 UUID를 잘라 무작위성 대부분을 없애는 코드, 암호학적이지 않은 난수원으로 조용히 대체하는 라이브러리 같은 것들입니다. 여기서 쓰는 비트는 브라우저의 암호학적 난수 생성기에서 나옵니다. 다른 곳에서는 라이브러리가 실제로 무엇을 쓰는지 짐작하지 말고 확인하는 습관이 유용합니다.
저장하는 방법
텍스트 형식은 36자이지만 값 자체는 16바이트입니다. UUID를 char(36)으로 저장하면 공간이 두 배 이상 늘어나고, 모든 보조 인덱스가 기본 키의 사본을 갖는 데이터베이스에서는 그 비용이 스키마 전체로 곱해집니다.
PostgreSQL에는 16바이트로 저장하면서 입력으로 텍스트 형식을 받는 기본 uuid 타입이 있습니다. MySQL에는 그런 타입이 없어서 binary(16)을 쓰고 경계에서 변환하는 것이 일반적입니다. 버전 1 UUID에는 시간 바이트의 순서를 바꿔 정렬 가능하게 만드는 잘 알려진 요령이 있는데, 버전 7에서는 그럴 필요가 없습니다.
URL에 UUID를 보여 주는데 길이가 거슬린다면 값을 줄이지 말고 같은 16바이트를 base32나 base64url로 인코딩하세요. 잘라 내는 방식은 문제가 터지는 순간까지 멀쩡해 보입니다.
버전 전체 정리
버전 1은 60비트 타임스탬프와 기기의 MAC 주소를 담습니다. 덕분에 정렬이 가능하고 출처를 추적할 수 있는데, 바로 뒤의 성질 때문에 외면받게 되었습니다. 어떤 기기가 식별자를 만들었는지 드러나기 때문입니다. 버전 2는 DCE 보안 변형으로 거의 아무도 쓰지 않습니다. 버전 3과 버전 5는 결정적입니다. 네임스페이스와 이름을 각각 MD5와 SHA-1로 해시하므로 같은 입력은 언제나 같은 식별자를 만들어 냅니다.
버전 4는 순수한 무작위이며, 조율도 시계도 기기 정보도 필요 없다는 이유로 어디서나 기본값이 되었습니다. 버전 6은 타임스탬프가 제대로 정렬되도록 버전 1을 재배치한 것이고, 버전 7이 현대적인 해답입니다. 상위 비트에 밀리초 단위 타임스탬프를 두고 그 뒤에 무작위 값을 붙이는 방식으로, 2024년 RFC 9562에서 표준화되었습니다.
버전 5는 지금보다 더 많이 쓰일 자격이 있습니다. 이미 가지고 있는 무언가, 예컨대 URL이나 파일 경로, 테넌트 이름에서 안정적인 식별자를 만들어야 할 때, 그것을 해시해 UUID로 바꾸면 조회 테이블이나 조율 단계 없이도 모든 시스템이 동일한 식별자를 얻습니다.
대안들, 그리고 더 잘 맞는 상황
ULID는 타임스탬프와 무작위 값을 Crockford base32 26자에 담습니다. 정렬이 가능하고 대소문자를 구분하지 않으며 텍스트 형태가 UUID보다 짧습니다. 버전 7보다 먼저 나와 같은 문제를 해결하므로, 새로 만드는 시스템이라면 대개 v7을 쓰면서 표준 안에 머무르면 충분합니다.
원래 Twitter에서 나온 Snowflake 식별자는 타임스탬프와 머신 id, 시퀀스 번호를 64비트에 채워 넣습니다. 작고 정렬 가능하며 조율 부담도 적지만, 노드마다 서로 다른 id를 부여해야 한다는 대가가 따릅니다. 또한 JavaScript의 number가 정확히 표현할 수 있는 범위를 넘기 때문에, 이를 쓰는 API는 문자열로 반환합니다.
nanoid는 URL에 넣기 좋은 짧은 무작위 문자열을 만들며 알파벳을 설정할 수 있습니다. 기본값인 21자는 v4 UUID와 대략 같은 엔트로피를 절반의 길이에 담습니다. 그리고 평범한 자동 증가 정수도 여전히 많은 시스템에서 최선의 선택입니다. 가장 작고 인덱싱이 가장 빠르며, 식별자가 내부에만 머문다면 전혀 문제가 없습니다.
식별자를 공개하면 무엇이 드러나는가
공개 URL에 순차 정수를 쓰면 거래량이 모두에게 알려지고, 누구나 범위를 훑을 수 있게 됩니다. /orders/1001이 있다는 것은 /orders/1000도 있다는 뜻이니까요. 여기에 권한 검사 누락이 겹치면 안전하지 않은 직접 객체 참조라 불리는 취약점이 됩니다. 이는 지금도 웹 애플리케이션에서 가장 흔하게 발견되는 결함 중 하나입니다.
무작위 v4 UUID는 열거를 막아 주지만, 막아 주는 것은 그뿐입니다. 이것은 접근 제어가 아닙니다. 아무도 추측할 수 없는 식별자라도 손에 넣은 사람은 누구나 쓸 수 있는 식별자이고, URL은 리퍼러 헤더와 브라우저 기록, 로그, 스크린샷, 공유된 링크를 통해 새어 나갑니다. 모든 요청은 여전히 이 사용자가 이 리소스를 볼 수 있는지 확인해야 합니다.
버전 7은 그 불투명함을 조금 되돌려 줍니다. 레코드가 대략 언제 만들어졌는지 드러나고, 연속된 식별자는 순서와 생성 속도를 알려 줍니다. 한 주에 몇 명의 고객이 가입했는지 추정하기에 충분한 정도입니다. 공개 식별자에서 둘 중 하나라도 민감하다면, 내부 기본 키로는 v7을 쓰고 외부에는 v4나 별도의 슬러그를 노출하세요.
기본 키, 인덱스, 저장 공간
B-트리 인덱스는 항목을 정렬된 상태로 유지하므로, 무작위 키는 삽입을 인덱스 전체에 흩뿌립니다. 인덱스가 메모리에 더 이상 들어가지 않게 되면 삽입마다 쓰기뿐 아니라 읽기까지 발생하고, 페이지는 고르지 않게 차서 분할되며, 테이블이 커질수록 쓰기 처리량이 떨어집니다. 순차 키는 끝에 덧붙고 한 페이지만 뜨겁게 유지하면서 이 모든 문제를 피합니다. 이것이 버전 7을 쓰는 이유의 전부입니다.
저장 방식도 중요합니다. UUID는 16바이트지만 텍스트 형태는 36자입니다. PostgreSQL에는 네이티브 uuid 타입이 있어 바이너리 형태로 저장하고 입력으로 텍스트도 받습니다. MySQL에는 그런 타입이 없어서 BINARY(16)을 쓰고 애플리케이션 경계에서 변환하는 것이 관례입니다. 대신 텍스트로 저장하면 공간이 두 배 이상으로 늘고, 모든 보조 인덱스가 기본 키 사본을 함께 담는 엔진에서는 그 비용이 스키마 전체에 걸쳐 불어납니다.
흔히 쓰는 절충안은 내부 조인에는 작은 정수 기본 키를 쓰고, 외부 식별자로는 UUID에 고유 인덱스를 거는 것입니다. 물리적 배치를 촘촘하게 유지하면서도 바깥 세계에는 속을 알 수 없는 핸들을 줄 수 있습니다.
코드에서 안전하게 생성하기
직접 조립하지 말고 플랫폼이 제공하는 함수를 쓰세요. 브라우저와 Node에서는 crypto.randomUUID, Python에서는 uuid4, Java에서는 UUID.randomUUID입니다. 이들은 암호학적 생성기에서 값을 가져오며, 충돌 확률에 대한 논리가 성립하는 것도 그 덕분입니다. Math.random으로 물러나는 라이브러리는, 실제로 일부 오래된 JavaScript 패키지가 그랬는데, 겉보기는 똑같지만 예측 가능한 식별자를 만들어 냅니다.
현실적인 실패는 통계적인 것이 아닙니다. 같은 스냅샷에서 복제한 가상 머신은 동일한 엔트로피 풀로 시작할 수 있고, 임베디드 기기는 부팅 시점에 엔트로피가 거의 없을 수 있습니다. 그리고 컬럼에 맞추려고 UUID를 잘라 내는 코드는 무작위성의 대부분을 파괴하면서도 여전히 식별자처럼 보이는 무언가를 남깁니다.
나머지는 두 가지 습관으로 피할 수 있습니다. 식별자는 대소문자를 구분하지 않고 비교하세요. RFC 9562가 소문자로 출력하되 어느 쪽이든 받아들이라고 정하고 있기 때문입니다. 그리고 Windows 도구가 붙이는 중괄호와 urn:uuid: 접두사는 제거하세요. 또한 형태만 믿지 말고 입력 시점에 검증하세요. URL에 들어 있는 UUID처럼 생긴 문자열도 결국 사용자 입력이기 때문입니다.
자주 묻는 질문
- UUID v4와 v7의 차이는 무엇인가요?
- v4는 122비트 난수로만 구성되어 아무것도 드러내지 않고 예측할 수 없습니다. v7은 앞에 48비트 밀리초 타임스탬프가 오고 그 뒤에 난수가 붙으므로 나중에 생성된 ID가 항상 뒤에 정렬됩니다. v4는 생성 시각을 감추고, v7은 시각이 드러나는 대신 정렬됩니다.
- 데이터베이스 기본 키에는 어떤 것을 써야 하나요?
- v7입니다. 무작위 v4 키는 인덱스 전체에 흩어져 삽입되므로 B-트리 페이지가 조각나고 쓰기 성능과 캐시 효율이 떨어집니다. v7 키는 순서대로 추가되어 전역 고유성을 유지하면서도 자동 증가 정수에 가깝게 동작합니다.
- 암호학적으로 안전한 난수인가요?
- 네. Math.random이 아니라 브라우저의 암호학적 난수 생성기인 crypto.getRandomValues를 사용합니다. v4는 122비트의 난수를 가지므로 충돌은 현실적인 문제가 아닙니다.
- v1이나 v5는 왜 없나요?
- v1은 MAC 주소와 시계 상태를 포함해 호스트 신원이 노출되며, RFC 9562도 새 시스템에는 v7을 권장합니다. v3과 v5는 이름 기반으로 네임스페이스와 이름을 해시해 결정적 ID를 만들기 때문에 별도의 입력이 필요하고, 독립된 도구가 더 적합합니다.
- UUID가 서버에서 생성되나요?
- 아닙니다. 브라우저에서 생성되며 전송되지 않으므로 이 사이트를 포함해 누구도 생성된 값을 볼 수 없습니다.
- UUID와 GUID는 어떻게 다른가요?
- 실질적으로 같습니다. GUID는 동일한 128비트 식별자에 대한 마이크로소프트식 명칭이며, 그래서 윈도우 도구에서는 중괄호로 표시되곤 합니다. 그 형식이 필요하면 중괄호 옵션을 사용하세요.
마지막 업데이트:
진행 단계, 예상 결과, 실제 결과를 적어 주세요. 비밀 정보와 개인 정보는 삭제해 주세요.
