N2
NanToo
開発7 分で読める

UUID v1 / v4 / v7 の使い分け — RFC 9562 に基づく実用ガイド

UUID(Universally Unique Identifier)は IETF の RFC 9562(2024年5月、旧 RFC 4122 を廃止・更新)で定義される 128 ビット識別子です。長らく使われてきた v1 / v4 に加え、2024 年の改訂でv6 / v7 / v8 が正式に標準化されました。本記事では各版のビット構造と、どのケースでどれを選ぶべきかを整理します。

#UUID#RFC 9562#データベース#ID設計
AD

UUID の基本構造

UUIDは常に128ビット(16オクテット)で、標準の表記は 8-4-4-4-12 の 16 進数(例: 550e8400-e29b-41d4-a716-446655440000)です。

RFC 9562 は構造を次の領域に分割します:

  • version: 上位側の4ビット(13番目のニブル)。1〜8 がバージョン番号
  • variant: その次の2〜3ビット。ほぼすべて "10xx" (RFC 9562 変種)
  • 残りの116〜118ビット: バージョンごとに意味が変わる

つまりUUIDの種類は13番目のニブルで即座に判別できます550e8400-e29b-41d4-... の "4" が v4、"7" なら v7 です。

v1 — 時刻とMACアドレス

v1 は 1582年10月15日(グレゴリオ暦の基準日)からの 100ns 単位のタイムスタンプ 60 ビットと、ノード識別子(通常はMACアドレス)48ビットで構成されます。

time-low : 32bit   ←低位ビット
time-mid : 16bit
time-hi  : 12bit + version(4bit)
clock-seq: 14bit + variant(2bit)
node     : 48bit   ←MACアドレスまたは乱数

利点: 時刻順にソート可能(ただしビット順序が直感に反するため並べ替え注意)。分散システムでの衝突が極めて少ない。

欠点: MACアドレスが埋め込まれるためプライバシー漏洩リスクがある。RFC 9562 では乱数ノードによる代替も許可されていますが、レガシー実装に注意。

v4 — 完全ランダム

v4 は 122ビットがすべて乱数(version 4ビットと variant 2ビットを除く)です。最もシンプルで、現在最も広く使われています。

衝突確率: 122ビットの乱数空間(約 5.3 × 10³⁶)は極めて広く、誕生日のパラドックスを考慮しても、10億個を生成した場合の衝突確率は約 10⁻²⁰ と実質ゼロです。

利点: 実装が単純、プライバシー懸念なし。

欠点: 時刻順序がないため、データベースのクラスタインデックスに使うと挿入位置が散らばり、Bツリーのページ分割が頻発してパフォーマンス低下を招く(特に MySQL InnoDB、SQL Server など)。

v7 — Unix時刻プレフィックス + 乱数

v7 は RFC 9562 で新たに標準化された、データベース用途に最適化されたUUIDです。

unix_ts_ms : 48bit   ←Unix時刻(ミリ秒)
version    : 4bit    (= 7)
rand_a     : 12bit   ←乱数
variant    : 2bit    (= 10)
rand_b     : 62bit   ←乱数

上位 48 ビットが Unix 時刻のため、辞書順=時刻順になります。これにより:

  • Bツリーインデックスへの挿入が末尾追加になり、ページ分割が起きにくい
  • 時間範囲クエリや、直近レコード取得が効率化される
  • v4 の乱数の良さ(予測困難性、プライバシー)を 74 ビット分残している

2025年以降の新規プロジェクトでは v7 を第一選択にすることが多くの実装者から推奨されています。PostgreSQL 18 には uuidv7() 関数がネイティブで追加予定(現時点では執筆時点の情報で、正式機能化の状況は要確認)。

v6 と v8 は何者か

v6: v1 の時刻ビットを並べ替えて「辞書順 = 時刻順」にした版。v1 互換性を保ちつつソート性を得たい場合に使う中間形。新規採用する理由は少なく、v7 が登場した今ではv6 より v7 を推奨されるケースがほとんどです。

v8: カスタム用途のためのプレースホルダ。RFC 9562 では「実装が自由にビット割り当てを決めてよい」としていますが、version と variant ビットは守る必要があります。特殊な要件(例: テナントIDを埋め込みたい)がある場合の拡張ポイントとして使用します。

NIL UUID と Max UUID

RFC 9562 は 2 つの特殊 UUID を定義します。

  • NIL UUID: 全ビット 0 (00000000-0000-0000-0000-000000000000) — "未設定"のセンチネル値
  • Max UUID: 全ビット 1 (ffffffff-ffff-ffff-ffff-ffffffffffff) — RFC 9562 で新規追加。範囲の上限として使える

これらは version/variant ビットの規則を破るため、厳密には"invalid"な UUID ですが、仕様が特例として認めています。ID未設定状態の初期値や、range クエリの境界値として役立ちます。

用途別の選択指針

用途推奨理由
DB の主キー(MySQL/PostgreSQL/SQL Server) v7 クラスタインデックスと相性が良く、時刻範囲クエリが効率的
公開 API のリソースID v4 または v7 推測困難性を保ちつつ、ソート性が要るなら v7
セキュリティトークン v4 時刻が漏れない完全ランダムが安全(厳密には専用の暗号乱数API推奨)
分散システムのログ相関ID v7 時系列で追える + 衝突しない
レガシーシステム互換 v1 / v4 既存実装が広く対応しているため
独自のプレフィックス情報を埋め込みたい v8 ビット割り当てをカスタマイズ可能

実装時の落とし穴

  1. 文字列保存 vs バイナリ保存: 文字列 36 バイトを CHAR(36) で保存する実装が多いですが、バイナリ 16 バイト(BINARY(16) / UUID型)のほうが 2〜3 倍コンパクトでインデックス効率も上がります。PostgreSQL の uuid 型、MySQL 8.0 の UUID_TO_BIN() を検討してください。
  2. 時刻取得の精度: v7 のミリ秒精度では、同一ミリ秒内に複数発行すると 12+62=74 ビットの乱数部に頼ることになります。極端な高頻度発行(毎ミリ秒 10万件以上)ではライブラリ実装の衝突対策を確認してください。
  3. v4 の乱数品質: Math.random() は暗号論的に安全ではありません。ブラウザなら crypto.randomUUID()、Node.js なら crypto.randomUUID()、Python なら uuid.uuid4() を使いましょう。

まとめ

  • RFC 9562(2024)が現行仕様。旧 RFC 4122 は廃止
  • version は13番目のニブルで即判別できる
  • v1: MAC+時刻、プライバシーリスク / v4: 完全ランダム、インデックス非効率
  • v7: Unix時刻プレフィックス+乱数。2025年以降のDB主キー推奨
  • v6 は v1 互換の中間形、v8 はカスタム拡張
  • NIL UUID(全0) / Max UUID(全1) は境界値として有用
  • 暗号乱数APIを使い、可能ならバイナリで保存

参考文献・ソース

NanToo 編集部

本記事は NanToo(ナンツー)運営の株式会社ヨネマスが編集・公開しています。 ツールの開発現場で得た知見をもとに、実務で役立つ内容を発信しています。

🔧 関連ツール

📚 関連記事

AD