UUID とは何か?
UUID(Universally Unique Identifier、汎用一意識別子)は、マイクロソフトの用語では GUID(Globally Unique Identifier、グローバル一意識別子)とも呼ばれる 128 ビットの数値で、コンピュータシステム内の情報を一意に識別するために使用されます。次の値を発行する中央機関が必要な自動インクリメントの整数とは異なり、UUID はどのマシンでも、いつでも独立して生成でき、衝突の可能性は天文学的に低くなっています。
UUID の標準テキスト表現は次のようになります:
550e8400-e29b-41d4-a716-446655440000
^^^^^^^^ ^^^^ ^^^^ ^^^^ ^^^^^^^^^^^^
時刻 時刻 バージョン バリアント ノード
このハイフン付き文字列は 32 個の 16 進数字(128 ビット)を xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx という形式にエンコードしています。ここで:
- M はバージョンビット(1、3、4、5、または 7)
- N はバリアントビット(RFC 4122 準拠の UUID では 8、9、a、または b)
歴史
汎用一意識別子の概念は、1980 年代後半に Apollo Computer と **Digital Equipment Corporation(DEC)**において、**ネットワーク・コンピューティング・システム(NCS)と分散コンピューティング環境(DCE)**の一部として生まれました。目的は、中央レジストリなしに分散システムが識別子を作成できるようにすることでした。
マイクロソフトはこの概念を COM/OLE に採用し、GUID と名付けましたが、基礎となる構造は UUID と全く同じです。両用語は実際には同義で使われています。
正式な仕様は RFC 4122 として 2005 年 7 月 に IETF から発行されました。5 つの UUID バージョン(v1–v5)を標準化し、バリアントビットを定義しました。それから 20 年後、RFC 9562(2024 年 5 月発行)が RFC 4122 に取って代わり、UUID v6 と v7 を正式に追加して、初期バージョンのデータベースパフォーマンスの問題を解決しました。
UUID の構造:128 ビットの詳細
UUID は常にちょうど 128 ビット(16 バイト)です。文字列として書式化すると、36 文字(32 個の 16 進数字 + 4 個のハイフン)になります。
フィールド ビット数 16 進数字 説明
──────────────────────────────────────────────────────────────
time_low 32 8 タイムスタンプの下位 32 ビット(v1)
time_mid 16 4 タイムスタンプの中間 16 ビット(v1)
time_hi_and_version 16 4 タイムスタンプ上位 12 ビット + 4 ビットバージョン
clock_seq_hi_res 8 2 バリアントビット + クロックシーケンス上位
clock_seq_low 8 2 クロックシーケンス下位
node 48 12 ノード ID(v1 では MAC アドレス、それ以外はランダム)
バージョンは time_hi_and_version フィールドの最上位 4 ビット(13 番目の 16 進文字)にエンコードされています。バリアントは clock_seq_hi_res の最上位 2〜3 ビット(17 番目の 16 進文字)にエンコードされています。
UUID バージョン別詳細
バージョン 1 — 時刻ベース
UUID v1 は 60 ビットのタイムスタンプ(1582 年 10 月 15 日からの 100 ナノ秒間隔)とホストの MAC アドレス、そしてクロックシーケンスを組み合わせています。
例:6ba7b810-9dad-11d1-80b4-00c04fd430c8
長所: 時間情報が含まれており、生成時刻でおおまかに並べ替えできます。
短所: MAC アドレスが埋め込まれており、プライバシーの懸念があります。UUID がいつ、どこで生成されたかが分かってしまいます。公開向けの識別子には推奨されません。
バージョン 2 — DCE セキュリティ
UUID v2 はタイムスタンプの一部を POSIX UID/GID とドメイン識別子に置き換えます。DCE 1.1 仕様で定義されていますが、一意性の保証を犠牲にしてドメイン埋め込みを行うため、実際にはほとんど使用されていません。
バージョン 3 — 名前ベース(MD5)
UUID v3 は 名前空間 UUID と名前を MD5 でハッシュすることで、決定論的な UUID を生成します。
例:5df41881-3aed-3515-88a7-2f4a814cf09e(名前空間 DNS + "example.com")
使用例: 同じ論理リソースに対して安定した再現可能な識別子を生成します。同じ名前空間と名前を 2 回ハッシュすると、常に同じ UUID が得られます。
注意: MD5 は暗号学的に弱いです。新しいシステムでは v5 を推奨します。
バージョン 4 — ランダム(最も人気)
UUID v4 は 122 ビットの暗号論的乱数データを使用します(残りの 6 ビットはバージョンとバリアントに使用)。
例:9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
これは最も広く使用されている UUID バージョンです。調整もネットワークアクセスもホストに関する知識も不要です。ランダム性は CSPRNG(暗号論的に安全な疑似乱数生成器)から得られます。
バージョン 5 — 名前ベース(SHA-1)
UUID v5 は概念的に v3 と同一ですが、MD5 の代わりに SHA-1 を使用します。
例:886313e1-3b8a-5372-9b90-0c9aee199e5d(名前空間 DNS + "example.com")
使用例: 決定論的で再現可能な UUID が必要で、MD5 より強いハッシュを使いたい場合。SHA-1 はすべての目的で暗号学的に安全とは言えませんが、衝突耐性は MD5 より大幅に優れています。
バージョン 7 — 時刻順(データベースに最適)
UUID v7 は RFC 9562 の目玉機能です。最上位ビットに **48 ビットの Unix タイムスタンプ(ミリ秒単位)**を埋め込んでおり、UUID を k-ソータブルにします。後で生成された UUID は、先に生成された UUID の後にソートされます。
例:018e8f5a-2b3c-7d4e-8f9a-0b1c2d3e4f50
最初の 12 個の 16 進文字(018e8f5a-2b3c)がミリ秒のタイムスタンプをエンコードし、残りはランダムです。これにより、v7 はデータベースの主キーとして理想的です。v4 のグローバルな一意性を保ちながら挿入順序が保証されるため、B ツリーインデックスの断片化が大幅に削減されます。
衝突確率:誕生日問題
UUID v4 は 122 ビットのランダム性を使用します。衝突確率は誕生日問題の公式で推定できます:
p(n) ≈ 1 − e^(−n²/2N)
ここで N = 2^122 ≈ 5.32 × 10^36(UUID の総可能数)。
少なくとも 1 回の衝突が起こる確率が 50% になるには、約 **2.71 × 10^18(27.1 京)**個の UUID を生成する必要があります。毎秒 10 億個の UUID を生成する速度なら、約 86 年かかります。
実用的には、UUID v4 の衝突は極めて起こりにくく、すべての現実のアプリケーションで不可能とみなすことができます。
UUID vs. 自動インクリメント ID
| 機能 | UUID v4 | 自動インクリメント |
|---|---|---|
| 一意性のスコープ | グローバル | ローカル(テーブルごと) |
| 生成場所 | クライアント側 | サーバー側 |
| 予測可能性 | 予測不可 | 連続して予測可能 |
| URL 安全性 | あり(エンコード必要) | あり |
| DB インデックスパフォーマンス | 低い(ランダム) | 優れている(連続) |
| マージ/レプリケーション | 容易 | 競合が起きやすい |
| ストレージサイズ | 16 バイト(バイナリ) | 4〜8 バイト |
| 人間の可読性 | 低い | 高い |
| セキュリティ(列挙攻撃) | 安全 | 脆弱 |
自動インクリメント ID は、分散要件のない単一データベースアプリケーションには十分です。UUID は次の場合に優れています:
- 複数のサービスやデータベースが独立して ID を生成する必要がある場合。
- 異なるソースからのレコードをマージする可能性がある場合。
- URL での連続 ID の公開を避けたい場合(列挙攻撃の防止)。
データベースパフォーマンス:v4 vs. v7
データベースにおける UUID v4 の問題
UUID v4 はランダムであるため、新しい行は B ツリーインデックスのランダムな位置に挿入されます。これにより次の問題が発生します:
- インデックスページの分割 — 順序不同の挿入を受け入れるためにデータベースが頻繁にインデックスページを分割しなければならない。
- キャッシュスラッシング — ランダムなアクセスパターンがバッファプールを無効化し、頻繁なディスク読み取りを引き起こす。
- 書き込み増幅 — 連続した挿入と比較してはるかに多くの I/O。
大きなテーブル(1000 万行以上)でのベンチマークでは、挿入が多いワークロードにおいて UUID v4 主キーが連続キーと比べて 3〜5 倍遅いという結果が多く見られます。
UUID v7 が解決する方法
UUID v7 は同じミリ秒ウィンドウ内で単調増加します。新しいレコードは自動インクリメントと同様に、インデックスの末尾または末尾近くに挿入されます。結果として:
- ほぼゼロのインデックス断片化。
- 最適なバッファプール利用率。
- 自動インクリメントに匹敵する挿入パフォーマンス。
推奨: グローバルに一意な主キーが必要な新しいデータベーススキーマには UUID v7 を使用してください。
分散システムでの活用例
UUID は分散 ID の基盤です:
- マイクロサービス: 各サービスが中央シーケンスサーバーと通信せずに ID を独立して生成できます。
- イベントソーシング: イベントは再生や再構成時も一意であり続ける不変の ID を取得します。
- CQRS: クライアント生成の UUID によってコマンドとクエリを相関させることができます。
- マルチリージョンデータベース: 異なるリージョンで作成されたレコードは決して衝突せず、結果整合性とマージ操作が簡単になります。
- 冪等性キー: API はクライアント生成の UUID を冪等性キーとして使用し、リクエストを安全に再試行できます。
- コンテンツアドレッサブルシステム: UUID v3/v5 はシステム間で同じリソースの安定した識別子を生成できます。
ULID:モダンな代替手段
ULID(Universally Unique Lexicographically Sortable Identifier)は UUID のコミュニティ開発の代替手段で、デフォルトで URL セーフかつ常にソート可能です。
| 機能 | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| ソート可能 | いいえ | はい | はい |
| URL セーフ | エンコード必要 | エンコード必要 | はい(Crockford Base32) |
| 時刻コンポーネント | なし | あり(ミリ秒) | あり(ミリ秒) |
| 標準 | RFC 9562 | RFC 9562 | コミュニティ仕様 |
| フォーマット長 | 36 文字 | 36 文字 | 26 文字 |
| ミリ秒内単調増加 | いいえ | オプション | はい |
ULID は 01ARZ3NDEKTSV4RRFFQ69G5FAV のような形式——26 文字、ハイフンなし、URL でそのまま使用可能。RFC 互換性を気にせず、ソート可能性と URL セーフ性をすぐに使いたい場合は ULID を検討する価値があります。
各言語での UUID 生成方法
JavaScript / TypeScript(Node.js とブラウザ)
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid';
const randomId = uuidv4(); // "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
const sortableId = uuidv7(); // "018e8f5a-2b3c-7d4e-8f9a-0b1c2d3e4f50"
// ネイティブ crypto(Node 14.17+ / モダンブラウザ)
const nativeId = crypto.randomUUID(); // UUID v4
Python
import uuid
# v4 — ランダム
print(uuid.uuid4()) # "f47ac10b-58cc-4372-a567-0e02b2c3d479"
# v5 — 名前空間 + 名前から決定論的に生成
print(uuid.uuid5(uuid.NAMESPACE_DNS, 'example.com'))
# "886313e1-3b8a-5372-9b90-0c9aee199e5d"
Go
import "github.com/google/uuid"
id := uuid.New() // UUID v4
v7, _ := uuid.NewV7() // UUID v7(google/uuid v1.6+)
fmt.Println(id.String())
Java
import java.util.UUID;
UUID v4 = UUID.randomUUID();
System.out.println(v4); // "3e4666bf-d5e5-4aa7-b8ce-cefe41c7568a"
Rust
use uuid::Uuid;
let v4 = Uuid::new_v4();
let v7 = Uuid::now_v7();
println!("{}", v4);
println!("{}", v7);
ベストプラクティス
- 汎用一意識別子には v4 を使用する — 順序が重要でない場合、v4 が最も簡単です。
- データベースの主キーには v7 を使用する — インデックスの断片化を避け、自然な並べ替え順序を得られます。
- 決定論的 ID には v5 を使用する — 同じ入力が常に同じ UUID を生成する必要がある場合(例:URL によるコンテンツの重複排除)。
- プライバシーに敏感な状況では v1 を避ける — MAC アドレスと作成時間が埋め込まれます。
- データベースでは UUID をバイナリ(16 バイト)として保存する — varchar(36) ではなく、スペースを節約しインデックスパフォーマンスを向上させるため。
- DCE Security を実装する場合を除き v2 を使用しない。
- API の境界で UUID 入力を検証する — 不正な形式の識別子の注入を防ぐため。
- 十分にテストされたライブラリを使用する — 独自の UUID 生成を実装するのではなく、エントロピーのソースは間違えやすいです。
よくある質問
Q:UUID と GUID は同じものですか?
A:機能的には同じです。GUID はマイクロソフトが同じ 128 ビット識別子フォーマットに付けた名前です。構造は全く同じで、用語だけが異なります。
Q:2 つの UUID が同じになることはありますか?
A:理論的にはあり得ますが、v4 の場合その確率は無視できるほど小さいです。衝突する確率が 50% になるには 27.1 京個の UUID を生成する必要があります。実際には衝突に遭遇することはありません。
Q:データベースの主キーにはどの UUID バージョンを使うべきですか?
A:UUID v7 が最良の選択です。時刻順(B ツリーインデックスに有利)、グローバルに一意、RFC 9562 で標準化されています。ライブラリがまだ v7 に対応していない場合は、ULID または「COMB」UUID パターンを代替として使用できます。
Q:UUID v4 はセッショントークンとして十分安全ですか?
A:UUID v4 は 122 ビットのランダム性があり、一般的には十分です。ただし、専用のセッショントークンライブラリの方がわずかに多くのエントロピーやより良いエンコーディングを使用することがあります。高セキュリティが必要な場合は、専用のトークン生成器を使用することをお勧めします。
Q:SQL データベースで UUID を効率的に保存するにはどうすれば良いですか?
A:利用可能な場合はネイティブの UUID カラム型(PostgreSQL、MySQL 8+)を使用するか、BINARY(16) カラムを使用します。2.25 倍のスペースを占有しインデックスが遅い CHAR(36) / VARCHAR(36) は避けてください。
Q:UUID v3 と v5 の違いは何ですか?
A:どちらも名前ベースで決定論的です。v3 は MD5 を使用し、v5 は SHA-1 を使用します。SHA-1 は衝突耐性が高いため、新しいシステムでは v5 が推奨されます。どちらもセキュリティクリティカルなハッシュには使用しないでください。
Q:UUID v7 は UUID v4 の代わりになりますか?
A:データベースの主キーとしては、はい——v7 は厳密に v4 より優れています。順序が無関係な他の使用例(API キー、トークン、相関 ID など)では、v4 でも全く問題ありません。
まとめ
UUID はモダンなソフトウェアエンジニアリングの基礎的なプリミティブです。バージョン間の違いを理解することで、適切なトレードオフを行うことができます:
- v4 — 調整なしで汎用的な一意性に最適。
- v5 — 決定論的で再現可能な識別子に最適。
- v7 — 時刻順のためデータベースの主キーに最適。
モノリス、マイクロサービスアーキテクチャ、グローバル分散データベースのいずれを構築する場合でも、UUID はデータを一意かつ安全に識別するための実戦テスト済みの標準化された方法を提供します。