UUIDに再入門してみる
はじめに
最近XでUUIDを話題にしているポストをいくつか見かけました。
そういえばUUIDなんとなく使ってるなと思ったので、自分のために再調査と整理をしたいと思います。
UUIDとは
UUID(Universally Unique Identifier)とは、128ビットの長さの一意識別子。
仕様はRFC 4122とRFC 9562によって定義されています。
| 項目 | RFC 4122 | RFC 9562 |
|---|---|---|
| 公開年 | 2005 | 2024 |
| 位置づけ | UUIDの初期標準 | UUIDの最新版仕様 |
| 状態 | 廃止 | 現行 |
| 定義 | v1,v2,v3,v4,v5 | v6,v7,v8 |
- v1:時刻 + MACアドレス
- v2:DCE Security(実質未使用)
- v3:名前ベース(MD5)
- v4:ランダム
- v5:名前ベース(SHA-1)
- v6:v1を改良した時系列UUID
- v7:Unix Epoch + Random(最重要)
- v8:アプリ定義カスタムUUID
RFC
フォーマット
UUIDは以下の8-4-4-4-12の形式で表されます。(全体で36文字、ハイフン除くと32文字の16進数)
550e8400-e29b-41d4-a716-446655440000
| | | | | | | | | |
time_low mid hi clk node
128ビットは5つのフィールドに分割され、Mがバージョン(v4なら4、v7なら7)、Nがバリアント(通常8,9,a,bのいずれか)を示します。
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
v1
- 構造
- 60bit:タイムスタンプ(100ns単位)
- 48bit:ノードID(通常 MACアドレス)
- 特徴
- 時系列ソート可能
- 分散環境で衝突しにくい
- 問題点
- MACアドレス露出
v2
- 特徴
- v1 を拡張し UID/GID を含む
- DCE(分散計算環境)向け
- 問題点
- 実装・利用例ほぼゼロ
v3
- 構造
- MD5 ハッシュ
- 特徴
- 同じ入力 → 同じUUID
- 問題点
- 一意ではない
- MD5使用のため新規用途では避けられる
v4
- 構造
- 122bit ランダム
- CSPRNG 前提
- 特徴
- 衝突確率が極小(ほぼない)
- 時系列の順序性を持たない
- 問題点
- DBインデックス効率が悪い
v5
- 構造
- v3 と同様だが SHA-1 使用
- 特徴
- v3 の上位互換
- 問題点
- 一意ではない
v6
- 構造
- v1 のタイムスタンプを上位ビットに再配置
- 特徴
- v1 の後継
- v1 → v7 への橋渡し的存在
- 問題点
- v7があるため、現在は存在意義薄目
v7
- 構造
- 48bit:Unix Epochタイムスタンプ(ms単位)
- 74bit:ランダム値
- 特徴
- 時系列ソート可能(B-Treeインデックスと相性が良い)
- MACアドレス不要でプライバシーの問題がない
- RFC 9562 で新規採用が推奨されている
- 問題点
- ミリ秒精度のため、同一ミリ秒内の順序はランダム部に依存
v8
- 構造
- バージョンビット以外の122bitをアプリケーションが自由に定義
- 特徴
- 組織やシステム固有の要件に対応可能
- 独自のタイムスタンプ精度やエンコーディングを組み込める
- 問題点
- 相互運用性がない(自分たちだけのルール)
- 独自実装のため衝突管理も自己責任
ULIDとの違い
ULID(Universally Unique Lexicographically Sortable Identifier)はUUID v7と似た特徴を持つ識別子です。
タイムスタンプ(ms)48bit + ランダム値80bitをCrockford Base32でエンコードします。
時系列準でソート可能で、26文字とUUIDと比べて短くなります。
どれを使うのか
新規で採用するなら、基本的にv4かv7のどちらかになるかと思います。
長いこともあり、脳死で採用するようなものでもないと思います。
DBの設計によっては、オートインクリメントで済ます場合もあるかと思います。
v4を選ぶケース
- 時系列の順序性が不要な場合(例:APIキー、セッションID、トークンなど)
- 生成タイミングの情報を外部に漏らしたくない場合
v7を選ぶケース
- DBの主キーとして使いたい場合
- 時系列ソートが必要な場合(例:イベントログ、メッセージIDなど)
DBインデックスへの影響
v4は完全ランダムなため、B-Treeインデックスにおいてページ分割が頻発し、書き込みパフォーマンスが低下します。また、キャッシュヒット率も下がるため読み取りにも影響します。
v7はタイムスタンプが上位ビットに配置されるため、新しいレコードがインデックスの末尾に追加されやすくなり、B-Treeとの相性が良くなります。
ただし、UUIDは16バイトとBIGINT(8バイト)の2倍のサイズがあるため、大量のレコードを扱う場合はオートインクリメントの整数型のほうが効率的なケースもあります。UUIDを採用する主な理由は、分散環境での一意性の保証やIDの予測困難性にあるため、要件に応じて使い分けるのが良いと思います。
例
01HY9A8QZ5M4R2WJ3X9K6F0N7B
01HY9A8QZ6A8TQ9R7C2M4K5L8
01HY9A8QZ7Z9P6X1D8E4N2JQ
生成ライブラリ
仕様に沿って実装すればライブラリを使わずとも実装は可能でしょうが、多くの場合はライブラリを使うと思います。
ざっと調べた感じでは、基本的にどの言語でもありそうですね。
※対応やサポート状況は各自で確認してください。
おわりに
v7までしか知らず、v8まであるの知らんかった。
