基礎知識 · テキスト・データ・セキュリティ
UUID とは?
結論
UUID(universally unique identifier)は、ID を発行する中央管理者なしでデータに付けられる 128 ビットの番号です。f47ac10b-58cc-4372-a567-0e02b2c3d479 のように、32 桁の16進数を5つのグループに分けて書きます。現行の規格 RFC 9562 は複数のバージョンを定めており、バージョン 4 は完全なランダム、バージョン 7 は先頭がタイムスタンプなので作成順に並べ替えられます。
UUID の読み方
テキスト形式は 36 文字で、16進数 32 桁とハイフン4つを 8-4-4-4-12 にまとめたものです。バイナリでは 16 バイトです。どの UUID にも、メタデータを表す位置が2つあります。
- バージョン:3つ目のグループの最初の桁です。
f47ac10b-58cc-4372-a567-0e02b2c3d479では4です。 - バリアント:4つ目のグループの最初の桁です。標準の UUID では
8、9、a、bのいずれかで、この例ではaです。
Microsoft が GUID と呼んでいるものも同じ形式です。16進数の大文字と小文字は同じ意味で、RFC 9562 は出力は小文字、入力は大文字小文字を区別しないことを推奨しています。特別な値が2つあり、すべてゼロの Nil UUID と、すべて f の Max UUID です。
UUID のバージョン
2024年5月に公開された RFC 9562 は RFC 4122 に代わるもので、バージョン 1〜5 を引き継ぎ、6、7、8 を追加しました。
| バージョン | 構成要素 | 時刻順に並べられるか | 主な用途 |
|---|---|---|---|
| 1 | タイムスタンプ(1582年からの 100 ns 単位)+ クロックシーケンス + ノード ID(多くは MAC アドレス) | テキストのままでは不可 | レガシーシステム |
| 3 | 名前空間と名前の MD5 ハッシュ | 不可 | 決定的な ID(v5 推奨) |
| 4 | 122 ビットの乱数 | 不可 | 汎用の ID |
| 5 | 名前空間と名前の SHA-1 ハッシュ | 不可 | 同じ入力から常に同じ UUID を得たいとき |
| 6 | バージョン 1 のフィールドを、時刻の上位から並べ替えたもの | 可 | v1 からの移行 |
| 7 | ミリ秒単位の 48 ビット Unix 時刻 + 乱数ビット | 可 | データベースのキー、ログ、イベント |
| 8 | ベンダーが定義する独自の構成 | 場合による | 実験的な用途やアプリ固有の用途 |
バージョン 3 と 5 は決定的です。DNS 名用の名前空間を example.com とともにハッシュすると、どのマシンでも同じ UUID になります。MD5 と SHA-1 はビットを散らすために使っているだけで、セキュリティ目的ではありません。
バージョン 4 とバージョン 7 の違い
| ビット | バージョン 4 | バージョン 7 |
|---|---|---|
| 0–47 | 乱数 | ミリ秒単位の Unix タイムスタンプ |
| 48–51 | バージョン 0100(4) | バージョン 0111(7) |
| 52–63 | 乱数 | 乱数、またはミリ秒未満のカウンター |
| 64–65 | バリアント 10 | バリアント 10 |
| 66–127 | 乱数 | 乱数 |
| 乱数のビット数 | 122 | 最大 74 |
RFC に載っているバージョン 7 の例は 017f22e2-79b0-7cc3-98c4-dc0c0c07398f です。最初の 12 桁 017f22e279b0 は Unix エポックから 1,645,557,742,000 ミリ秒後、つまり 2022-02-22 19:22:22 UTC を表します。
実用上の違いはデータベースの性能です。ランダムな v4 のキーは B-tree インデックスのランダムな位置に入るため、ページ分割が起こり、書き込みがインデックス全体に散らばります。時刻順の v7 のキーは自動採番の整数と同じように末尾付近に追加されるので、挿入が速く、インデックスもコンパクトに保てます。新しく作るデータベースの主キーには、たいてい v7 のほうが適しています。作成時刻を見せたくない不透明なトークンには v4 を使いましょう。
どれくらい「一意」なのか
乱数が 122 ビットなので、バージョン 4 の UUID は約 5.3 × 10^36 通りあります。誕生日の上界によれば、1回でも衝突する確率が 50% に達するには約 2.7 × 10^18 個が必要です。毎秒 10 億個を生成しても約 86 年かかります。
現実の衝突は、計算上の確率ではなく質の悪い乱数から生じます。同じ乱数状態のまま複製された仮想マシンや、Math.random() のような暗号学的でない関数を使った生成器では、値が重複することがあります。暗号学的に安全な乱数源を使いましょう。ブラウザーと Node.js なら crypto.randomUUID()、Python なら uuid.uuid4()、PostgreSQL なら gen_random_uuid() です。
UUID の保存方法
- PostgreSQL の
uuidのようなネイティブ型があれば使いましょう。テキストの 36 バイトではなく 16 バイトで済みます。 - MySQL では
BINARY(16)で保存しましょう。テキストで保存するとインデックスのサイズが2倍以上になります。 - UUID は大文字小文字を区別せずに比較するか、入力時に小文字にそろえましょう。
- v4 の UUID から意味を読み取ろうとしないでください。情報を持っているのはバージョンとバリアントのビットだけです。
よくある質問
UUID と GUID の違いは何ですか?
実質的にはありません。GUID は Microsoft が使っている名前で、UUID と同じ 128 ビットの形式とバージョンに従っています。
2つの UUID が同じになることはありますか?
理論上はありえますが、安全な乱数源で生成していれば実際にはありません。バージョン 4 の UUID が 100 京個(10^18)をはるかに超えるまでは、衝突の確率は無視できるほど小さいままです。
どのバージョンの UUID を使うべきですか?
データベースのキーなど時刻順が役立つものにはバージョン 7、ID から何も読み取れないようにしたいときはバージョン 4、同じ入力から常に同じ ID を得たいときはバージョン 5 です。
JavaScript で UUID を生成するには?
crypto.randomUUID() を呼び出します。暗号学的に安全な生成器からバージョン 4 の UUID を返します。HTTPS のページであれば最近のブラウザーで動作し、Node.js 19 以降ではグローバルに使えます。