UUIDはなぜ重複しないのか?
UUIDはなぜ重複しないのか?
Webアプリケーションなどのシステム開発では、データに一意の識別子を付与する必要があります。たとえば データベースの主キー は、ジムでいうところの「会員カード番号」。誰かと同じ番号だとパーソナルトレーナーの予約を取り違えるような事故が起きます。
他にも「ロッカーの鍵」「筋トレ記録ノートのページ番号」「プロテインシェイカーの名前シール」など、「絶対にダブってはいけない」ものがあることは、筋肉系エンジニアの皆さんも想像に難くないことでしょう。
そんな時に役立つのが UUID(Universally Unique Identifier)です。これは、ほぼ重複しないIDを生成できる仕組みです。本記事ではUUIDの仕組みを解説し、Pythonでの実装を通じて「なぜUUIDはほとんど重複しないのか?」を見ていきます。
UUIDとは?
UUIDは、IETF(Internet Engineering Task Force)によって定義された標準化された一意の識別子で、正式名称は「Universally Unique Identifier」です。これは128ビットの数字で、通常はハイフンで区切られた16進数の5つのパート(8-4-4-4-12)からなる36文字の文字列として表されます。
例:
0a09bc33-1ac0-4dfd-bc16-60f1d001bba6
一見、非常にシンプルではありますが、実はこのUUIDには、精巧なアルゴリズム設計が隠されています。
最も特徴的な点としては、システム内で一意のキー(識別子)を生成できる点にあります。極端な話、ネットワーク接続がなくても、異なるデバイスで生成されたUUIDが重複する可能性はほぼなく、一意のUUIDを生成するために、わざわざ中央システムにアクセスする必要がないのです。
つまり「それぞれのジムで会員カードを発行しても、番号がかぶらない」ようなイメージです。
UUIDは7種類ある?
実は、UUIDの生成方法にはいくつかの種類があることをご存知でしょうか?
- v1:現在のタイムスタンプ、クロックシーケンス、およびノード(通常はMACアドレス)を組み合わせることで生成されます
- v2:v1を基盤にPOSIX UID/GIDを導入したもので、ほとんど使用されません
- v3:ネームスペースと名前をMD5ハッシュアルゴリズムで計算します
- v4:生成に完全にランダムな数値に依存します
- v5:v3に類似していますが、SHA-1ハッシュアルゴリズムを使用します
- v6:v1を改良し、フィールドの順序を変更することで「時系列での並び替え性能」を高めた形式です
- v7:UNIXエポックのタイムスタンプ(ミリ秒精度)とランダム値を組み合わせた形式で、ソート可能かつ衝突確率も極めて低く、最新の標準として推奨されつつあります
実際の開発では、v1とv4が最もよく使用されます。v1は時間順の並べ替えが必要なシナリオに適しており、v4はランダム性に対する要件が高いシナリオに適しています。最近では、ソート性能とランダム性を両立するv7を採用するケースも増えてきています。
PythonによるUUID生成
Pythonの標準ライブラリにあるuuidモジュールは、さまざまなvのUUIDを生成するための関数を提供しています。その使い方を確認してみましょう:
import uuid
# v1 UUID (タイムスタンプとMACアドレスに基づく)
uuid1 = uuid.uuid1()
print(f"v1 UUID: {uuid1}")
# v3 UUID (ネームスペースと名前に基づくMD5ハッシュ)
namespace = uuid.NAMESPACE_URL
name = "https://example.com"
uuid3 = uuid.uuid3(namespace, name)
print(f"v3 UUID: {uuid3}")
# v4 UUID (ランダム生成)
uuid4 = uuid.uuid4()
print(f"v4 UUID: {uuid4}")
# v5 UUID (名前空間と名前に基づくSHA-1ハッシュ)
uuid5 = uuid.uuid5(namespace, name)
print(f"v5 UUID: {uuid5}")
出力例:
v1 UUID: 286a5c30-7c8b-11f0-bf4b-32238ee92ea5
v3 UUID: 68794df6-5e20-385f-ab08-bb73f8a433cb
v4 UUID: ae3265e3-8601-4db1-979f-ad94ac0e5ec3
v5 UUID: 4fd35a71-71ef-5a55-a9d9-aa75c889a6d0
2025年8月現在で、Pythonの標準ライブラリuuid
には、まだv6とv7は実装されていません。
利用するには、uuid6
ライブラリを使用すると良いでしょう。
pip install uuid6
import uuid6
# v6: Version 1 をベースにした時系列ソート可能UUID
uuid_v6 = uuid6.uuid6()
print("UUID v6:", uuid_v6)
# v7: UNIXタイムスタンプ+ランダム性
uuid_v7 = uuid6.uuid7()
print("UUID v7:", uuid_v7)
出力例:
UUID v6: 1ebc4180-9f13-6b00-b65e-7d4fcd3a0e52
UUID v7: 01929d1d-89a0-7c5d-9f0e-56d97b4f9f2b
UUIDの仕組み
v1の原理
-
60ビット: 1582年10月15日からの100ナノ秒単位のタイムスタンプ。 1582年10月15日は、グレゴリオ暦が採用された日です。
-
14ビット: クロックシーケンス(時計の逆行対策)。システム時計を遡及する場合、時計のシーケンスは増加するように設計されており、タイムスタンプが小さくなっても生成されるUUIDが一意であることを保証します。
-
48ビット: ノード識別子。通常はMACアドレスです。MACアドレスを使用すると、異なるデバイスで生成されたUUIDが衝突しないようにできますが、プライバシー問題も生じます。そのため、一部の実装ではMACアドレスの代わりにランダムに生成されたノード識別子を使用します。
MACアドレスを使うことで、異なるデバイス間でも重複を避けられるというわけですね。
v4の生成原理
v4 UUIDの生成は比較的単純で、主にランダム数に依存しています:
- 122ビットの乱数+6ビットの固定情報 で構成されます。
このランダム性こそが一意性の鍵です。
「完全ランダムで重複しないの?」と思うかも知れませんが、後ほどその重複可能性について詳細に説明します。
v3とv5のUUIDの生成原理
v3と5のUUIDは名前空間と名前に基づいて生成され、その原則は類似しています:
-
名前空間UUIDと名前をバイト列に変換
-
連結してハッシュ(MD5またはSHA-1)を計算
-
結果から128ビットを取り出し、UUID形式に整形
同じ名前空間+名前なら必ず同じUUIDが生成されることになります。
v6のUUIDの生成原理
-
Version 1 と同じデータ(タイムスタンプ+MACアドレスまたはノードID+クロックシーケンス)から生成
-
フィールドの順序を再配置し、「時間順ソートが効く構造」に最適化され、データベースの索引でソート性能を向上させやすい形式に。
v7のUUIDの生成原理
-
UNIXエポックに基づくミリ秒精度のタイムスタンプ+ランダム性を組み合わせた構造
-
時系列ソート可能かつランダム性も高く、実用的なバランス型になっている
-
48ビットのUNIXエポックミリ秒タイムスタンプ+74ビットのランダム/シーケンス部など
B-tree インデックスでの挿入効率とキャッシュ局所性を改善する点が利点です。
UUIDがほぼ重複しない理由
UUIDの唯一性は、以下の点を通じて保証されています:
1. 巨大な空間
UUIDは128ビットであり、つまり合計で2^128 = 約3.4×10^38個のUUIDが存在することになります。
参考までに:
-
地球上の砂粒の数は約7.5×10^18
-
観測可能な宇宙の星の数は約10^22
-
2^128 は観測可能な宇宙の恒星の数の約 3.4×10^16 倍
これは、1 秒あたり 1 兆個の UUID を生成した場合でも、すべての可能な UUID を消費するのに約 10^18 年かかる ことを意味し、これは宇宙の年齢(約 138 億年)をはるかに上回る期間です。
これなら、仮に、地球上の全てのダンベルに1個ずつ番号を振ったとしても、100億年以上は耐えられますね!
2. 適切に設計されたランダム性
v4のUUIDでは、すべての122ビットがランダムに生成されます。確率論によると、2つのランダムに生成されたUUIDが重複する確率は極めて低いです。具体的には、n個のUUIDを生成した後の重複確率は以下の近似式を用いて計算できます:
P(n) ≈ n² / (2×2^128)
n=10^12の場合、衝突確率は約10^24 / (2×3.4×10^38) ≈ 1.47×10^-15となり、ほぼ無視できる確率 です。
ベンチプレスで同じ重さをジムにいる全員が同時に選ぶ確率より低いです。
3. 時間と空間における一意性
v1のUUIDでは、同じデバイスで生成されたUUIDの一意性を保証するためにタイムスタンプの増加に加え、ノード識別子(通常はMACアドレス)も異なるデバイスで生成されたUUIDの一意性を保証します。MACアドレスは世界中で一意であるため、継続的に増加するタイムスタンプと組み合わせることで、UUIDの一意性は時間と空間の両次元から保証 されます。
4.タイムスタンプによる一意性
v1はタイムスタンプ+MACアドレスで一意性を保証しています。
つまり「異なる時間に違う会員がトレーニングしてもロッカー番号がかぶらない」仕組みです。
実用上の重複確率
理論上はUUIDが重複する可能性がありますが、実践的な応用においては、この確率は非常に低いため無視できる程度です。具体的な数値を見てみましょう:
- 10億個のv4 UUIDを生成 → 重複確率:約10^-18
- 1兆個のv4 UUIDを生成 → 衝突確率:約10^-15
- 世界中の人が毎秒100万個のUUIDを100年間生成した場合 → 衝突確率:約10^-8
「隕石に当たる確率よりも、2つの同一のUUIDを生成する確率の方が低い」と言われたりもします。
「ジムでバーベル落として足に当たる確率」よりも遥かに低いことは想像に難くないでしょう。
UUIDの使いどころ
UUIDは、その特性により、多くのシナリオで非常に有用です。
-
分散DBの主キー
-
キャッシュキー
-
分散ログのトレーシングID
-
メッセージキューのID
-
ファイル名の一意化
-
セッションID
UUIDのメリットとデメリット
メリット
-
グローバル一意性
-
中央管理不要
-
クロスプラットフォーム対応
-
v1なら生成時刻の概算が可能
デメリット
-
長く(36文字)扱いづらい
-
(v5以前)ソートには不向き
-
人間にとって読みづらい
まとめ
UUIDは、優れた一意の識別子生成方式です。分散システムにおいて生成される識別子が、広大な空間、適切に設計されたランダム性、および時間と空間の組み合わせにより、ほぼ重複しないことが保証 されています。
タイムスタンプとMACアドレスに基づくv1の生成方法か、乱数に基づくv4の生成方法か、最先端のv7か、それぞれに適したシナリオがあると思います。実際の開発では、具体的な要件に応じて適切なUUIDのバージョンを選択するのが良いと思います。
反面、記事中に挙げたデメリットなども存在します。読みにくさやランダム性によって、問題が生じる可能性もあることは否定できません。ただ、それらを上回るメリットがあることもまた事実でしょう。
Discussion