3 ポイント 投稿者 GN⁺ 2024-08-27 | 1件のコメント | WhatsAppで共有
  • 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が必要な場合もユーザーがすでに把握している可能性が高い

1件のコメント

 
GN⁺ 2024-08-27
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 仕様の文書を 2 つ引用しています。ただし RFC4122 は、私の好みには暗号文のようすぎました
      0-7 Variant 空間に定義された歴史的な UUID タイプについては、人々の理解に役立つ情報提供 RFC の作業を始めているところです。議論や文言レビューをしたいなら、https://github.com/yocto/draft-yocto-uuid を見てください
  • 詳細は 2 分で見つけられました。記事のリンクをクリックして RFC 9562 の DCE 定義部分へ行き、その段落の最初のリンクから仕様に入り、「UUID」を検索して付録 A に行けば全部あります
    名前は紛らわしく「Universal Unique Identifier」ですが、必要な内容はすべてあります。自分の記事に貼ったリンクくらいは、自分でクリックしてみるとよいでしょう

    • ここで学べることは 2 つあります。ひとつは 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 は、2 つの次元の交点を示す可逆的な方式だった
      任意の 2 台のマシンが同じ 2 つの入力に対して同じ UID/UUID を作り、識別されたメッセージを受け取った側は、識別子を元の構成要素に戻すことができた。一時的なメッセージのラベルとして設計されていたため、2 つの次元は時刻とハードウェア ID で、当初は Apollo のシリアル番号、後には Ethernet のハードウェアアドレスなどが使われた
      混乱のかなりの部分は、初期の AEGIS 実装で Apollo のエンジニアたちがファイルシステム識別に「canned」、つまり静的でよく知られた UID を使い始めたことに由来すると思う。時間がたつにつれ、UUID の一般的な用途は、重複が意図された一時的な識別子から、重複を避けるべき canned な識別子へと完全に移り、2 つの次元もランダムと、もう一つのランダムになった
      歴史はもっと複雑だ。Microsoft は Windows NT 向けの MSRPC を作るために Apollo の中心人物の一人を雇い、そのため GUID も生まれた。GUID はフィールド配置が UUID と異なり、多くの資料が言うのとは違って混合エンディアンではない。Microsoft は一時的な RPC メッセージ識別用 GUID だけでなく、COM クラス、メディアコーデックなど、よく知られた識別子が必要なほぼあらゆるものに canned GUID を好んで使う。例: https://gix.github.io/media-types/
      同じコメント欄で自分のリポジトリを 2 回リンクして申し訳ないが、自分の 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 ビットの数値で、「ドットで区切った 4 つのまとまり」がその表現の一つであるのと似ている。UUID を文字列形式として考えているなら、UUID についての最も基本的な概念から間違っている。単にランダムな識別子が欲しい場合でも、ランダム UUID に「これはランダムであることを意図している」という小さなフラグビットがあるのは良いことだと思う。文脈のない識別子を一つだけ見つけたときに役立つ
    • UUID v4 はバージョン 4 であることを示すために 4 ビットも固定値に設定する
      複数の生成方式の間に異なる名前空間を作ることが有用かどうかは議論できるが、一般的な乱数生成器が有効な UUIDv4 を作る確率は 1/16 にすぎない。もちろん自分で UUID 生成器を作りたいなら、ビットを正しく設定するのは些細なことだ
    • その通りだが、ほとんどの開発者にとって魅力的なのは実装が簡単な点だ。ほぼすべての言語に、1 行で動く 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 を生成できると非常に有用だが、それを実現する標準的な方法は知らない
    • セキュリティ関連の問題をなぜそんなに軽く流すのか分からない