터미널을 열고 uuidgen을 다섯 번 치거나, 아무 사이트나 열어서 새로고침을 다섯 번 하거나. UUID가 필요한 순간은 대개 다른 일을 하던 중간이라 둘 다 거슬린다. 주소창 옆 아이콘 하나로 끝내려고 확장 프로그램을 만들었고, 만들면서 “그 중간"이 구체적으로 어떤 상황들이었는지 정리해봤다.

1. 시드 데이터에 한 번에 여러 개

마이그레이션 테스트용 fixture를 손으로 쓰는 중이라고 하자. 한 개씩 받아서 붙여넣는 게 제일 짜증나는 패턴이다. 개수를 지정해서 한 번에 받는다.

2dab98b3-4321-413b-886f-ed096d6ad0aa
233ab7b6-c6f9-4ec7-91de-47fa20c752eb
cbcdb763-fd23-4525-b658-1e65b95bdbf7
173c9bc9-fe1a-4ba5-b9be-a7552b413006
255bcfa2-ddfe-42a5-9b20-df99524963ed

2. 컬럼이 CHAR(32)라 하이픈이 들어가면 안 될 때

MySQL에서 UUID를 CHAR(36) 대신 CHAR(32)로 잡아둔 스키마를 꽤 본다. 받아온 값에서 하이픈을 지우는 건 1초짜리 일이지만, 하루에 다섯 번 하면 다섯 번 거슬린다. 체크박스로 처리했다.

그대로      ddafde73-9a65-4471-8c75-ba5c6576567d   (36자)
하이픈 제거 ddafde739a6544718c75ba5c6576567d       (32자)
대문자      DDAFDE73-9A65-4471-8C75-BA5C6576567D

대문자 옵션은 SQL Server의 UNIQUEIDENTIFIER나 .NET Guid.ToString() 출력과 눈으로 대조할 때 쓴다. 값이 같은데 대소문자가 달라서 “다른가?” 하고 한 번 멈추는 일이 없어진다.

3. 형식만 맞으면 되는 API 경로 파라미터

GET /api/users/{id} 를 404 응답 모양만 보려고 호출할 때, id는 유효한 v4 형식 이기만 하면 된다. 라우터의 UUID 밸리데이터를 통과해야 404가 나오지, 아무 문자열이나 넣으면 400이 나와서 보려던 걸 못 본다.

GET /api/users/2580af44-7ec3-46a1-972a-8a6de19c973f

이 값은 버전 니블(4)과 variant 비트(9)까지 규격대로 들어간다. 직접 확인했다 — /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ 통과.

4. 삽입 순서대로 정렬돼야 하는 키라면 v7

여기가 v4/v7 토글이 있는 이유다. v4는 전부 난수라 정렬하면 삽입 순서와 무관하게 섞인다. v7은 앞 48비트가 밀리초 타임스탬프라 시간순으로 선다.

Node v23에서 각각 10,000개를 뽑아 인접쌍 역전 수를 셌다:

v4   26ms 동안 10,000개   역전쌍 5,007 / 9,999   (50.1%)
v7   23ms 동안 10,000개   역전쌍     0 / 9,999

v4의 50%는 운이 나빠서가 아니다. 난수 두 개를 비교하면 절반은 작게 나오는 게 당연하다. v7의 0은 그냥 타임스탬프 덕이 아니라 같은 밀리초 안에서도 순서를 유지하도록 rand_a 12비트에 카운터를 넣었기 때문이다. 카운터 없이 만들면 같은 밀리초에 몰린 값끼리는 다시 난수 비교가 되어 역전이 생긴다.

연속으로 뽑은 5개를 보면 앞 13자가 같은 걸 볼 수 있다. 그게 타임스탬프이고, 그 뒤가 카운터다.

01a12684-43b8-75cb-8769-bda79bd13033
01a12684-43b8-75cc-bd97-eaa2dbb8b77c
01a12684-43b8-75cd-a63e-e54b62e37394
01a12684-43b8-75ce-8971-3821124fbf5f
01a12684-43b8-75cf-99e6-776146029799

75cb → 75cc → 75cd → 75ce → 75cf. 정렬해도 순서가 그대로다.

v7 자체의 함정 — 같은 밀리초 정렬, 라이브러리마다 다른 구현 방식, PostgreSQL 18의 선택 — 은 분량이 꽤 돼서 따로 정리해뒀다: UUID v7 monotonicity .

권한은 0개다

manifest.json에 permissions 키 자체가 없다. 빈 배열이 아니라 키가 없다. host_permissions도, content_scripts도, background도 없다.

1{
2  "manifest_version": 3,
3  "name": "UUID Generator — v4 & v7",
4  "action": { "default_popup": "popup.html" },
5  "homepage_url": "https://withuse.io/uuid"
6}

그래서 설치할 때 “이 사이트의 모든 데이터를 읽고 변경” 같은 경고가 뜨지 않는다. 난수는 crypto.getRandomValues로 로컬에서 만들고, 네트워크 호출은 소스 전체에 한 줄도 없다(fetch·XMLHttpRequest grep 0건). 복사는 MV3에서 사용자 제스처가 있으면 clipboardWrite 권한 없이 navigator.clipboard.writeText()로 된다 — 이건 만들면서 알게 된 것이고, 아직도 이 권한을 선언하는 코드가 많다.

npm에서 가져온 것도 없다. UUID 생성 로직은 직접 썼고, 그래서 심사자가 읽는 소스가 곧 배포되는 소스다. 대신 값을 치렀다 — v7 카운터를 처음에 틀리게 짜서 같은 밀리초 정렬이 깨져 있었고, 테스트를 돌려보고서야 찾았다. 성숙한 라이브러리를 썼으면 없었을 버그다. 몇백 줄짜리 순수 로직이라 그 거래를 받아들였다.


웹스토어: UUID Generator — v4 & v7 브라우저에서 바로 쓰려면: withuse.io/uuid