3P by GN⁺ | ★ favorite | 댓글 1개
  • UUID는 v1부터 v8까지 8개 버전이 있으며, 숫자가 높다고 더 새롭거나 더 낫다는 뜻이 아니라 서로 다른 생성 방식이 RFC 9562에 정의돼 있음
  • 일반적인 선택지는 v4와 v7로 좁혀지며, v4는 무작위 ID의 기본값이고 v7은 생성 시간순 정렬이 필요한 경우에 적합함
  • v1과 v6은 같은 재료를 쓰지만, v6은 정렬하면 생성 시간순이 되도록 필드 순서를 바꾼 형태임
  • v3와 v5는 입력 데이터를 해시해 UUID를 만들며, v3는 MD5, v5는 SHA-1을 사용하고 후보 입력값으로 DNS와 URL을 둘 수 있음
  • 가능하면 v1·v6 대신 v7을 쓰고, 입력 데이터 기반 UUID가 필요하면 v5, 완전 사용자 정의 UUID가 필요하면 v8을 검토하는 흐름이 실용적임

UUID 버전별 생성 방식

  • UUID 버전은 v1부터 v8까지 있으며 모두 RFC 9562에 정의돼 있음
  • 시간 기반 UUID

    • UUID Version 1타임스탬프, 단조 카운터, MAC 주소로 생성됨
    • UUID Version 6은 v1과 같은 데이터를 쓰지만, 정렬 시 생성 시간순이 되도록 순서를 바꿈
    • UUID Version 7타임스탬프와 무작위 데이터로 생성됨
  • 무작위·사용자 정의 UUID

    • UUID Version 4는 완전히 무작위 데이터로 생성되며, 많은 사람이 UUID라고 떠올리는 형태에 가까움
    • UUID Version 8은 모든 버전에 필요한 version/variant 필드를 제외하면 완전히 사용자 정의 방식임
  • 입력 데이터 해시 기반 UUID

    • UUID Version 3는 사용자가 제공한 데이터의 MD5 해시로 생성됨
      • RFC의 후보 입력값에는 DNS와 URL이 있음
    • UUID Version 5는 사용자가 제공한 데이터의 SHA-1 해시로 생성됨
      • v3와 마찬가지로 DNS와 URL을 입력 후보로 둘 수 있음
  • 예약된 UUID

    • UUID Version 2는 보안 ID용으로 예약돼 있으며 알려진 세부 정보가 없음

실제 선택 기준

  • 대부분의 경우 선택지는 v4 또는 v7
  • 단순히 무작위 ID가 필요하다면 v4가 기본 선택지로 적합함
  • ID를 정렬할 수 있어야 한다면 v7을 고려할 수 있음
    • 예시로 UUID를 데이터베이스 키로 쓸 때 v7이 후보가 될 수 있음
  • v5 또는 v8은 UUID 안에 자체 데이터를 넣고 싶을 때 사용하는 쪽에 가까움
    • 이런 유형은 대체로 필요한 상황을 사용자가 이미 알고 있는 경우가 많음
  • RFC에 따르면 v7은 v1과 v6을 개선하므로 가능하면 v1·v6 대신 v7을 써야 함
    • v1 또는 v6이 꼭 필요하다면 v6을 사용할 수 있음
  • v2는 특정되지 않은 보안 용도로 예약돼 있음
  • v3는 더 강한 해시를 쓰는 v5로 대체됐으며, v3가 필요한 경우도 사용자가 이미 알고 있을 가능성이 큼

댓글과 토론

Hacker News 의견들
  • UUID v2의 세부사항이 없다는 건 악명 높게 불명확한 RFC만 읽었을 때의 얘기임: https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#t...
    거의 마주칠 일은 없지만 version 0 UUID도 있음. 이후 다른 “버전”들이 호환 방식으로 정의될 수 있게 한 예약 비트의 출처라서 언급할 가치가 있음. 관련 조사는 내 UUID 라이브러리에 정리해 둠: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
    멋있어서 지원하기로 했지만, 아직 날짜 롤오버와 더 오래된 Apollo UID 처리 방식은 정해야 함

    • RFC9562에는 공정하게 말해 UUID Version 2 명세 문서 두 개를 인용해 뒀음. 다만 RFC4122는 내 취향엔 너무 암호문 같았음
      0-7 Variant 공간에 정의된 역사적 UUID 타입에 대해서는 사람들이 이해하는 데 도움이 될 정보성 RFC 작업을 시작하는 중임. 논의나 문구 검토를 하고 싶다면 https://github.com/yocto/draft-yocto-uuid를 보면 됨
  • 세부사항은 2분 만에 찾을 수 있었음. 글의 링크를 눌러 RFC 9562의 DCE 정의 부분으로 가고, 그 문단 첫 링크로 명세에 들어간 뒤, “UUID”를 검색해서 부록 A로 가면 전부 나옴
    이름은 헷갈리게 “Universal Unique Identifier”지만 필요한 내용이 다 있음. 자기 글에 단 링크 정도는 직접 눌러보면 좋겠음

    • 여기서 배울 수 있는 건 두 가지임. 하나는 v2 UUID 명세를 읽는 것이고, 다른 하나는 UUID에서 생성 당시의 상황 정보를 알아내는 것임
      문맥상 다른 버전들이 특정 요소에서 생성된다고 설명돼 있으니, 해당 문장은 분명히 후자를 말하는 것임. 문장이 조금 불명확하긴 하지만, 오해를 유도한다고 보긴 어려움
    • 나도 정확히 같은 과정을 거쳤고, 세부사항에 신경을 별로 안 쓴 것 같아서 바로 글을 끝까지 안 읽었음
      그래도 부록은 당시의 스냅샷처럼 읽는 재미가 있었음
    • 아마 언어 모델이 쓴 글일 수도 있음
  • 73WakrfVbNJBaAmhQtEeDvbK7nP9xM 같은 짧은 UUID 표준이 있으면 좋겠음
    엄밀히는 어디선가 중복될 수 있으니 UUID는 아니겠지만, 무작위이면서도 외우기 충분히 짧은 ID 표준 조합을 원함

    • 가장 가까운 건 ULID가 떠오름. 26자 base32라 짧고, 128비트이며, 사전식 정렬도 가능함
      더 인기 있는 표준이 없는 이유는 뭔가를 포기해야 하기 때문이라고 봄. 128비트는 거의 모든 용도에서 충돌 위험이 낮지만, 더 작아질수록 구체적 상황과 충돌의 영향을 따져야 해서 표준으로 만들기 어려움. base64나 base85 같은 다른 인코딩을 쓰면 더 짧아지지만 대소문자 구분, URL 안전성 같은 걸 희생하게 됨: https://github.com/ulid/spec
    • 더 조밀한 UUID가 필요하면 기존 UUID를 URL 안전 base64로 바꿔 22자 형태로 표현할 수 있음
      같은 UUID의 다른 표현일 뿐이고 되돌릴 수 있음. UUID는 결국 128비트 값이라 실제 변환이라기보다 대체 표기임
    • Sqids가 맞을 수도 있음. UUID보다 훨씬 짧은 ID를 만들지만, 보편적으로 고유하지는 않고 정수 시퀀스에서 생성됨: https://sqids.org/
    • 보통 N비트 난수를 만들고 base58로 인코딩함. N은 원하는 만큼 고르면 됨
      일부 UUID 버전에 있는 단조 정렬 이점은 잃지만, base58은 URL에 안전하고 특수문자를 포함하지 않음. 값은 여전히 바이너리로 저장할 수 있음. 예를 들어 Postgres에서는 텍스트 컬럼 대신 bytea를 쓸 수 있음
    • 128비트 UUID를 더 짧은 텍스트 형태로 표현할 수 있는 대체 인코딩 기법을 표준화하는 작업 중임
      논의는 여기서 볼 수 있음: https://github.com/uuid6/new-uuid-encoding-techniques-ietf-d...
  • UUID v7의 타임스탬프는 Databend에 큰 변화였음. AWS S3에서 메타데이터 파일을 타임스탬프로 빠르게 찾는 데 쓰고 있어서 vacuum 같은 작업이 훨씬 빨라짐
    PR: https://github.com/datafuselabs/databend/pull/16049

    • 흥미롭긴 하지만, 시간을 기준으로 찾기 위해 UUID를 쓰는 건 잘 떠오르지 않음. 나라면 별도 타임스탬프 필드를 둘 것 같음
      시간순 정렬 UUID의 큰 장점은 지역성이 좋아진다는 데 있음. 인덱스에 새 항목을 넣을 때 대체로 뒤에 붙이는 작업이 되어 무작위 삽입보다 싸질 수 있음. 다만 경합이 늘 수도 있으니, 타임스탬프 앞에 일부 무작위 비트를 넣어 정렬된 “샤드”를 만드는 하이브리드도 고려할 만함. 읽기도 최신 데이터에 몰리는 경우가 많아서 최신 데이터가 한곳에 있고 캐시에 잘 올라오는 건 유용함
    • DynamoDB 같은 키-값 저장소에도 아주 좋음. 타임스탬프나 ISO 날짜를 접두사로 붙인 복합 키보다 훨씬 깔끔하고, 타임스탬프용 보조 인덱스를 낭비하는 것보다도 나음
  • uuid2의 목적을 이해하기 어렵다. 이런 타입들이 더 있다는 것도 몰랐고, Xandr에 내 개인정보 삭제를 요청할 때 uuid2를 처음 봤음: https://news.ycombinator.com/item?id=40913915
    Wikipedia를 읽어봐도 왜 “범용 고유 식별자”라는 걸 만들고도 여러 타입을 두는지, 그중 일부는 원래 PC까지 추적 가능하게 만들었는지 이해가 잘 안 됨. MAC 코드 일부를 섞으면 uuid2가 더 무작위에 가까워지는 건지, 다른 이유가 있는 건지 궁금함. 개인정보 보호 관점에서는 선택 가능한 문자를 아주 많이 둔 긴 식별자만 써서 중복 가능성을 사실상 없애면 안 되는지도 궁금함

    • 원래 목적은 Apollo 분산 컴퓨팅 아키텍처에서 메시지를 식별하는 것이었음. UID와 이후 UUID는 두 차원의 교차점을 표시하는 되돌릴 수 있는 방식이었음
      어떤 두 머신이든 같은 두 입력에 대해 같은 UID/UUID를 만들고, 식별된 메시지를 받은 쪽은 식별자를 원래 구성요소로 되돌릴 수 있었음. 일시적 메시지의 라벨로 설계됐기 때문에 두 차원은 시간과 하드웨어 ID였고, 처음에는 Apollo 일련번호, 나중에는 Ethernet 하드웨어 주소 등이 쓰였음
      혼란의 상당 부분은 초기 AEGIS 구현에서 Apollo 엔지니어들이 파일시스템 식별에 “canned”, 즉 정적·잘 알려진 UID를 쓰기 시작한 데서 온다고 봄. 시간이 지나며 UUID의 일반적 용도는 중복이 의도된 일시적 식별자에서, 중복을 피해야 하는 canned 식별자로 완전히 이동했고, 두 차원도 무작위와 또 무작위가 됨
      역사는 더 복잡함. Microsoft가 Windows NT용 MSRPC를 만들려고 Apollo 핵심 인물 중 한 명을 고용했고, 그래서 GUID도 생겼음. GUID는 필드 배치가 UUID와 다르며, 많은 자료가 말하는 것과 달리 혼합 엔디언이 아님. Microsoft는 일시적 RPC 메시지 식별 GUID뿐 아니라 COM 클래스, 미디어 코덱 등 잘 알려진 식별자가 필요한 거의 모든 것에 canned GUID를 즐겨 씀. 예: https://gix.github.io/media-types/
      같은 댓글란에 내 저장소를 두 번 링크해 미안하지만, 내 UUID 라이브러리 README에 이 역사를 정리하기 시작했고 다시 이어가야 함. Apollo는 1980년에 시작됐고 Leach/Salz UUID RFC 초안은 1998년에야 나왔으니, 현대 표준에는 빠진 얘기가 엄청나게 많음: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
  • UUID v4는 지정된 위치에 하이픈을 넣는 난수 바이트 생성기일 뿐임. 꼭 쓸 필요 없이 직접 난수 바이트를 만들면 공간을 아낄 수 있음
    불필요한 하이픈, 버전 정보 같은 것도 줄어듦

    • UUID는 128비트 숫자이고, 하이픈이 들어간 문자열 표현은 그 숫자를 나타내는 여러 방식 중 하나일 뿐임
      IPv4 주소가 32비트 숫자이고 “점으로 구분한 네 묶음”이 그 표현 중 하나인 것과 비슷함. UUID를 문자열 형식으로 생각한다면 UUID에 대한 가장 기본적인 개념부터 잘못 잡은 것임. 단지 무작위 식별자를 원한다 해도, 무작위 UUID에 “이건 무작위로 의도됐다”는 작은 플래그 비트가 있는 건 멋지다고 봄. 맥락 없는 식별자 하나만 발견했을 때 유용함
    • UUID v4는 버전 4임을 나타내려고 4비트도 고정값으로 설정함
      여러 생성 방식 간에 서로 다른 이름공간을 만드는 게 유용한지는 논쟁할 수 있지만, 일반 난수 생성기가 유효한 UUIDv4를 만들 확률은 1/16뿐임. 물론 직접 UUID 생성기를 만들고 싶다면 비트를 올바르게 설정하는 건 사소함
    • 맞지만 대부분 개발자에게 매력적인 점은 구현이 간단하다는 것임. 거의 모든 언어에 한 줄로 동작하는 UUID 라이브러리가 있음
      Go라면 uuid.New().String()이면 되지만, crypto/rand로 난수 데이터를 읽고 base64나 hex로 바꾸려면 더 많은 줄과 노력이 필요함
  • MAC 기반 버전은 쓰지 않는 걸 권함. 이론상 v4와 v7 외에는 다 해당될 수 있지만, v1이 최악임
    v3도 MD5가 심하게 깨졌다는 문제가 있음

    • MD5는 암호학적 해시 함수로서 “깨진” 것임. 비암호학적 해시 함수로는 여전히 완전히 괜찮음
  • 4번 외의 세부사항은 몰랐지만, 정말 유용하게 빠진 건 SHA256 데이터와 카운터를 쓰는 방식일 것 같음. PBKDF2와 비슷한 형태임
    개인정보를 보존하는 파생 식별자가 될 수 있고, 특정 UUID가 어떤 시드에서 파생됐다는 걸 느슨하게 증명할 수도 있음

    • 정말 필요하다면 UUIDv4를 인코딩 형식으로 보고, 암호 알고리즘으로 122비트 출력을 만든 뒤 UUID로 인코딩하면 됨
      그 외에는 더 긴 출력을 원하게 될 것임
    • v3와 비슷하지만 해시 알고리즘을 지정 가능하게 만든 형태임
  • 그냥 v7을 쓰면 됨
    이제 보안 전문가들이 아니라고 할 차례임

    • 생성일이 민감한 정보가 될 수 있거나 UUID가 완전히 추측 불가능해야 한다면 v4를 쓰면 됨. 그 외에는 v7을 쓰면 됨
    • UUID와 ULID에서 항상 아쉬웠던 건, 내가 아는 한 결정적으로 생성할 좋은 방법이 없다는 점임
      많은 용도에서는 데이터를 다시 처리해도 동일한 ID를 생성할 수 있으면 매우 유용한데, 이를 달성하는 표준 방식은 모름
    • 보안 관련 문제를 왜 그렇게 가볍게 넘기는지 모르겠음