はじめに
この記事の結論を先に書くと、UUIDのバージョンは特に理由がなければv4、DBの主キーに使うなら順序性のあるv7を検討するのがおすすめです。以下ではなぜこの結論に至ったのかをまとめていきます!
こんにちは!ソーイ株式会社の木寺です。
システム開発をしていると「UUID」という言葉を目にする機会は多いと思います。データベースの主キーやAPIのリクエストID、ファイル名の重複回避など、さまざまな場面で利用されています。
実際に弊社のシステムでも、モバイルアプリの端末を識別するためのIDや、分割された録画データを同じグループとして紐づけるための識別子としてUUIDが使われています。
私自身、これまではUUIDを「ランダムな長い文字列」くらいに捉えていましたが、生成の仕組みやバージョンごとの違いまでは詳しく知りませんでした。そこで今回、UUIDの仕組みや使い分けについて改めて調べ、まとめてみることにしました!
UUIDとは
UUID(Universally Unique Identifier)は、世界中のどこで生成しても、事実上重複しないとみなせる識別子です。GUID(Globally Unique Identifier)と呼ばれることもありますが、これはMicrosoftが使う呼称で、実質的に同じものを指しています。
UUIDはRFC 4122という仕様(2024年にはRFC 9562として更新されています)で標準化されており、128ビット(16バイト)の値で構成されます。
文字列として表現するときは、次のように8-4-4-4-12桁のハイフン区切りで書くのが一般的です。
550e8400-e29b-41d4-a716-446655440000
見た目は複雑ですが、要は「128ビットの数値」を人間が読みやすい16進数表記に変換し、ハイフンで区切っただけのものです。
UUIDの内部構造
UUIDの128ビットは、大きく分けると以下のようなフィールドで構成されています(バージョンによって使い方は異なります)。
- time_low / time_mid / time_hi_and_version:タイムスタンプに関連する部分(バージョン1や7で使用)
- version:3番目のグループの先頭1桁に、UUIDのバージョン番号(1〜8)が埋め込まれている
- variant:4番目のグループの先頭ビットに、UUIDのバリアント(仕様の系統)を示す情報が入っている
- node / random:残りのビット。MACアドレスやランダム値が入る
先ほどの例 550e8400-e29b-41d4-a716-446655440000 を見ると、3番目のグループが 41d4 から始まっており、先頭の 4 がバージョン4であることを示しています。UUIDの文字列を見るだけで「これはv4だな」と判別できるのはこの仕組みのおかげです。
なぜ「重複しない」と言えるのか
UUID自体は128ビットで構成されており、128ビット全体では2の128乗通りの値を表現できます。
ただし、後述するUUID v4では、このうち6ビットがバージョンとバリアントの情報として使われるため、実際にランダムに生成される部分は122ビットです。その組み合わせは2の122乗通りあり、非常に膨大な数になります。
1つの特定のUUIDと偶然一致する確率は約5.3×10³⁶分の1であり、これを小数で書くと0.000000000000000000000000000000000000188です。
これは「絶対に重複しないアルゴリズム」というより、「重複する確率が天文学的に低いので、実質的に重複しないとみなせる」という統計的な保証になります。
UUIDのバージョン
UUIDにはいくつかのバージョンがあり、生成方法が異なります。用途に応じて使い分けられているので、代表的なものを紹介します。
バージョン1:タイムスタンプ+MACアドレス
生成した日時(100ナノ秒単位のタイムスタンプ)とネットワークカードのMACアドレスを元に生成されます。生成順序がある程度わかる一方、MACアドレスから生成元のマシンが特定できてしまう懸念があるため、現在ではあまり積極的には使われていません。
バージョン2:DCE Security UUID
バージョン2は、DCE(Distributed Computing Environment)のセキュリティ用途で使われていたUUIDです。POSIXのユーザーID(UID)やグループID(GID)などを組み込める仕組みになっています。
現在のRFC 9562では、バージョン2は「DCE Security用」として予約されていますが、具体的な生成方法は定義されていません。現在の一般的なアプリケーション開発で利用する機会はほとんどないため、本記事では概要のみ紹介します。
バージョン3・5:名前ベース(ハッシュ生成)
同じ「名前空間+名前」の組み合わせからは、常に同じUUIDが生成されるという特徴を持つバージョンです。バージョン3はMD5、バージョン5はSHA-1でハッシュ化します。
「入力が同じなら出力も同じ」という決定的な性質があるため、例えば「このURLに対応するUUIDを一意に決めたい」といった、再現性が必要な場面で使われます。ハッシュ強度の観点から、新規に使うなら3よりも5が推奨されます。
バージョン4:ランダム生成
現在もっとも広く使われているバージョンです。バージョンビットとバリアントビットを除く122ビットをすべて乱数で埋めて生成します。生成元の情報を含まず、実装もシンプルなため、一般的な用途で広く使われています。
バージョン6:v1の並び替え版
バージョン1と同じ情報(タイムスタンプ+MACアドレス)を使いますが、タイムスタンプ部分を先頭に並べ直すことで、文字列としてソートしたときに時系列順になるように改良されたバージョンです。v1の弱点だった「ソートしにくい」という問題を解消していますが、後述のv7の登場もあり、採用例はまだ多くありません。
バージョン7:タイムスタンプ+ランダム(比較的新しい)
先頭48ビットにミリ秒単位のUnixタイムスタンプを含み、残りをランダム値で埋めるバージョンです。生成順に並びやすい性質があり、DBの主キー用途でも注目されています。
バージョン8:カスタム用途
仕様側でフォーマットを定義せず、実装者が自由にビットの意味を決められる「予約枠」のようなバージョンです。独自の要件に合わせたUUID互換フォーマットを作りたい場合に使われます。
バージョンまとめ
以下にここまでの内容を表として記載します。
| バージョン | 生成の元 | 生成順にソート | 生成元の特定 | 主な用途 |
|---|---|---|---|---|
| v1 | 時刻 + ノード情報 | △ | 可能性あり | 旧来の時刻ベースUUID |
| v2 | DCE Security固有の情報 | △ | 可能性あり | DCE Security(現在はほぼ使われない) |
| v3 | 名前空間 + 名前(MD5) | × | なし | 同じ入力から同じUUIDを生成したい場合 |
| v4 | 乱数 | × | なし | 一般的なUUIDとして幅広く利用 |
| v5 | 名前空間 + 名前(SHA-1) | × | なし | 同じ入力から同じUUIDを生成したい場合 |
| v6 | v1を並び替えた時刻ベースの情報 | ○ | 可能性あり | v1との互換性を保ちつつDBで扱いやすくしたい場合 |
| v7 | Unix時刻 + 乱数 | ○ | なし | DBの主キーなど、生成順に並べたい場合 |
| v8 | 実装者が自由に定義 | 設計次第 | 設計次第 | 独自・実験的な用途 |
各言語でUUIDを生成する方法
多くのプログラミング言語では、標準ライブラリまたは軽量なパッケージでUUIDを生成できます。
JavaScript(Node.js) の場合、標準のcryptoモジュールにrandomUUID()という関数が用意されており、追加のライブラリなしでv4のUUIDを生成できます。
Python の場合、標準ライブラリのuuidモジュールからUUIDを生成できます。従来のuuid1()、uuid3()、uuid4()、uuid5()に加え、Python 3.14以降ではuuid6()、uuid7()、uuid8()にも対応しています。
Java の場合、java.util.UUIDクラスのrandomUUID()メソッドでv4のUUIDを生成できます。
いずれの言語でも「UUID 生成 言語名」で調べれば、標準機能で簡単に扱えることがほとんどです。
UUIDが使われる場面
- データベースの主キー:オートインクリメントの数値IDと違い、複数のサーバーやサービスで採番してもぶつからない
- APIのリクエストID・トレースID:分散システムでログを横断的に追跡するときの目印として
- ファイル名・一時ファイルの命名:アップロードされたファイルの名前が衝突しないように
- オフライン環境でのID発行:ネットワークに繋がっていない端末同士でも、後で同期したときにIDが衝突しない
数値の連番IDとの比較
| 連番ID(AUTO_INCREMENT) | UUID | |
|---|---|---|
| 採番場所 | DB側で一元管理 | どこでも生成できる(分散環境向き) |
| 推測のしやすさ | 容易(1, 2, 3…) | 困難 |
| データサイズ | 小さい(4〜8バイト) | 大きい(16バイト、文字列だと36文字) |
| インデックス効率 | 良い | v4はランダムなので断片化しやすい(v7は改善) |
| 可読性 | 高い | 低い |
| セキュリティ(推測耐性) | 低い | 高い |
分散システムやマイクロサービスのように「複数の場所で同時にIDを発行したい」というケースではUUIDが有利です。一方、単一のDBで完結する小規模なシステムでは、連番IDのほうがシンプルでパフォーマンスも良いことが多いです。
採用時の注意点
v4はインデックス効率が悪くなりやすい
v4はランダムなビット列であるため、B-treeインデックスを使うDBに大量挿入すると、挿入位置がバラバラになりページ分割が頻発し、インデックスの断片化やキャッシュ効率の低下を招くことがあります。挿入性能やインデックスサイズが気になる場合は、時系列順にソートできるv7の採用を検討するとよいです。
文字列としてのサイズが大きい
UUIDを文字列(36文字)としてそのままDBに保存すると、数値IDに比べてストレージ容量やインデックスサイズが大きくなります。多くのDBではUUID型やバイナリ型(16バイト固定長)が用意されているので、そちらを使うことでオーバーヘッドを抑えられます。
v1・v6はプライバシーに注意
MACアドレスを含むv1やv6は、生成元の端末を特定する手がかりになり得ます。外部に公開するIDとして使う場合は、v4やv7など生成元情報を含まないバージョンを選ぶほうが安全です。
まとめ
- UUIDは128ビットの値で、実用上は重複しないとみなせる識別子
- 128ビットのうち一部はバージョン・バリアント情報として使われており、文字列を見るだけでバージョンを判別できる
- バージョンによって生成方法が異なり、通常はランダム生成のv4、DB主キー用途では順序性のあるv7が候補になる
- 分散環境やAPI連携が多いシステムほどUUIDのメリットが活きる
用途に応じて連番IDとUUIDを、さらにはUUIDの中でもバージョンを使い分けることが、設計上のポイントになりそうです。
参考
RFC 9562
https://datatracker.ietf.org/doc/html/rfc9562
Node.js crypto.randomUUID()
https://nodejs.org/api/crypto.html#cryptorandomuuidoptions
Python uuid モジュール
https://docs.python.org/3/library/uuid.html
Java java.util.UUID
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/UUID.html
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii
