「不可能」な衝突:UUID v4が本番環境で失敗するとき
ある開発者が最近、Hacker Newsに恐ろしい話を投稿しました。彼らのデータベースが重複したUUID v4を検出したというのです。システムには約15,000件のレコードしかありませんでしたが、1年という間隔を置いて作成された2つのドキュメントが、全く同じ識別子 b6133fd6-70fe-4fe3-bed6-8ca8fc9386cd を共有していました。
詳しくない人にとって、これは「マトリックス」のバグのように感じられるでしょう。これほど小さなデータセットでUUID v4が衝突する数学的な確率は、天文学的に低いです。およそ $2 \times 10^{-29}$ です。あるコミュニティメンバーが述べたように、それは「宇宙の寿命における衝突回数が、あなたの肝臓にある原子の数よりも少ない」というレベルです。
しかし、「統計的に不可能」なことが本番環境で起こる場合、それは純粋な偶然であることは稀です。それはほとんどの場合、エントロピーのシステム的な失敗、あるいはこれらの識別子がどのように生成されるかについての誤解の症状です。
数学 vs 現実
UUID v4は122ビットのランダム性に依存しています。暗号学的に安全な擬似乱数生成器(CSPRNG)を備えた完璧な世界では、衝突の確率は非常に低いため、エンジニアはそれを不可能として扱います。これはソフトウェアアーキテクチャにおいて危険な考え方につながります。つまり、ランダムに生成されたIDは、単にランダムであるという理由だけで一意であると仮定してしまうのです。
議論の中で経験豊富なエンジニアたちが指摘したように、ランダム性は「衝突耐性(collision-resistance)」を提供するものであり、「一意性の保証(guaranteed uniqueness)」を提供するものではありません。数学的な確率と、本番環境での保証の間には根本的な違いがあります。ランダムなIDに頼るということは、エントロピー源の品質に賭けているということなのです。
原因の追跡:なぜ衝突が起こるのか
小さなデータセットでUUIDの衝突に遭遇した場合、原因はほぼ間違いなく「不運」ではありません。代わりに、以下の一般的な失敗モードを探してください。
1. エントロピーの枯渇と不適切なシード値
UUIDは、それを動かす乱数の質に依存します。擬似乱数生成器(PRNG)のシード値が不適切に設定されている場合、予測可能なシーケンスが生成されたり、値が繰り返されたりすることがあります。これは特に以下の場合に一般的です:
- 仮想化環境: 一部のVMはエントロピーを「仮想化」してしまい、複数のインスタンスが同じ内部状態から開始されることがあります。
- プロセス・フォーク: 一部の言語や環境では、親プロセスがすでにPRNGを初期化している場合、子プロセスがその親からフォークされた際に、全く同じエントロピー状態を継承してしまい、同一のUUIDを生成してしまうことがあります。
- ハードウェアの欠陥: 稀ではありますが、実際のハードウェアのバグがエントロピー源の品質を損なうことがあります。
2. 決定論的な環境(「Googlebot」問題)
議論の中で提起された最も洞察に満ちた指摘の一つは、クライアントサイドでの生成に関するものです。もしUUIDがブラウザで生成される場合、あなたはクライアントのランタイムに左右されることになります。
例えば、Googlebotのような特定のクローラーは、決定論的な乱数を使用するJavaScriptを実行することがあると指摘されています。もしアプリケーションがクライアントサイドでUUIDを生成し、ボットがそのコードを実行した場合、異なるセッション間でIDが繰り返される可能性があります。
3. アプリケーション・ロジックとデータのライフサイクル
数学を疑う前に、配管(仕組み)を確認してください。「重複するUUID」の最も一般的な原因は、衝突そのものではなく、むしろ以下のようなものです:
- データの移行: CSVインポート、バックアップの復元、または環境の同期中の偶発的な重複。
- リトライ・ロジック: UUIDがリトライ・ループの「外側」で生成されているバグ。システムが挿入に失敗し、リトライを行いますが、その際に同じ変数を使用するため、衝突のように見える重複キーエラーが発生します。
回避不能な事態へのエンジニアリング
シニアエンジニアはどう衝突のリスクに対処すべきでしょうか?コミュニティの合意は明確です:一意性を仮定しないことです。
段階的な処理の実装
INSERT が常に成功すると仮定するのではなく、衝突を適切に処理するループでID生成をラップしてください。もしデータベースがユニーク制約違反を返した場合、新しいIDを生成して再試行してください。
代替のIDスキームを検討する
絶対的な一意性が求められる場合は、純粋なランダム性から離れてください:
- UUID v7: これにはタイムスタンプが含まれており、衝突の範囲を「全く同じミリ秒に生成されたID」にまで縮小できます。
- データベース生成のID: エントロピー源が堅牢なサーバーサイドのシステムによって管理されるよう、データベースにID生成を(例:PostgreSQLのネイティブなUUID関数を使用して)任せてください。
- 連番ID: 予測可能性が問題にならないシステムであれば、伝統的なオートインクリメント整数が、一意性の保証におけるゴールドスタンダードであり続けています。
最後に
ある寄稿者が述べたように、「十分な規模になれば、エッジケースは理論上の話ではなくなり、本番環境でのイベントとして発生し始めます」。ここでの教訓訓は、UUID v4が壊れているということではなく、「統計的に不可能」という仮定が、システムの基盤として危険であるということです。カーネルのバグ、決定論的なボット、あるいは不適切なシード値のVMであれ、現実世界は数学よりもはるかに混沌としています。システムを衝突耐性(collision-resistant)にするだけでなく、衝突許容(collision-tolerant)にするよう設計してください。