UUID v4 vs v7: IDにはどのバージョンを使うべきか
ランダムなv4 UUIDと時刻順に並ぶv7 UUIDは、異なる問題を解決します。なぜv7がたいてい優れた主キーになるのか、v4が今でも適切なのはどんなときか、そしてバージョンビットが実際に何を意味するのかを説明します。
新しい行、新しいファイル、あるいはキューに載せるメッセージに識別子が必要に なります。まず手が伸びるのはUUIDです。128ビットで、中央のカウンターなしに どこでも生成でき、ほぼ確実に一意です。しかし「UUID」が一つのものを指して いるわけではありません。どのバージョンを選ぶかによって、値の並び方、 データベースのキーとしての振る舞い、そして何が漏れるかが変わります。 2026年に重要なのはv4(ランダム)とv7(時刻順)の二つで、両者は互換的では ありません。
UUIDとは実際に何か
UUIDは128ビットで、通常は8-4-4-4-12の区切りで32桁の16進数として
書きます: 0190b8e4-...。このうち6ビットは固定のオーバーヘッドです。
4ビットがバージョン(どの生成方式が作ったか)を、2ビットがバリアント
(レイアウト標準)を表します。残りの122ビットがバージョンごとに異なる部分で、
ランダムなノイズ、タイムスタンプ、あるいはその混合です。UUIDを読むとき、
バージョンの数字は13番目の16進文字です。そこが4ならv4、7ならv7です。
0190b8e4-5e3a-7c2d-9f10-3a4b5c6d7e8f
^ version (7)
その一桁がすべてです。以下の内容はすべて、残りのビットを何が埋めるのか、 そしてそれがなぜ重要なのかについてです。
v4: ランダム
v4 UUIDは、バージョンとバリアントのビットが刻まれた122ビットのランダム値 です。構造も、タイムスタンプも、マシン識別子もありません。暗号学的に安全な ソースから得たエントロピーだけです。これによって二つの強力な性質が生まれます。 何の調整もなしにどこでも一つ生成でき、直前の値から次の値を推測できません。
コストはv4をデータベースの主キーとして使うときに現れます。ほとんどの データベースは主キーをB-treeに格納し、これがディスク上で行をキー順に保ちます。 ランダムなキーはランダムな位置に落ちるため、挿入がインデックス全体に書き込みを 散らし、ページ分割を起こし、バッファキャッシュをかき乱します。負荷の高い テーブルでは、これはスループットとストレージに測定可能な損失となります。 インデックスは断片化し、整列されたものより速く肥大化します。数千行であれば 気づくことはありませんが、数百万件の挿入になると気づくことになります。
ですからv4は、推測できないことが要点であるときに正しい選択です。URLに 入れるパスワードリセットトークン、列挙されたくない公開オブジェクトID、APIキー がそれにあたります。書き込みの多い主キーの既定値としては誤った選択です。
v1と時刻をめぐる回り道
v7以前、時刻ベースの選択肢はv1でした。60ビットのタイムスタンプにマシンの MACアドレスを加えたものです。二つの点がv1を沈めました。MACアドレスはどの マシンがそのIDを作ったかを漏らし、タイムスタンプのバイトがかき混ぜられた順序で 格納されるため、v1の値は文字列としてもバイトとしても時刻順に並びません。v1は 「時刻をエンコードする」という問題は解きましたが、「並べ替え可能である」という 問題は解かず、人々が実際に求めていた性質は後者でした。v1はレガシーとして扱えば よいでしょう。
v7: 時刻順
v7 UUIDは48ビットのUnixタイムスタンプをミリ秒単位で先頭に置き、残りを ランダム値で埋めます。タイムスタンプが最上位の部分でビッグエンディアンで 格納されるため、v7の値は生のバイト順で時刻順に並びます。したがって文字列形式 でも時刻順に並びます。順番に生成された二つのIDは、その順番で並びます。
この一つの変更がB-treeの問題を解決します。新しいキーは古いキーより大きいので、 挿入は散らばらずにインデックスの右端付近に追加されます。v4の「どこでも生成、 調整不要」という性質を保ちながら(後続のランダムビットが、同じミリ秒内の衝突を 依然として無視できるほど低くします)、インデックスの局所性と無料の時刻順の 並びを得られます。主キーにおいては、この組み合わせこそがv7が既定の推奨になった 理由です。
| v4 | v7 | |
|---|---|---|
| 内容 | 122ランダムビット | 48ビットmsタイムスタンプ + ランダム |
| 時刻順に並べ替え可能 | いいえ | はい (バイト順・文字列順) |
| PKとしてインデックスに優しい | いいえ (書き込みを散らす) | はい (ほぼ順次) |
| 生成時刻を明かす | いいえ | はい |
| 向いている用途 | 不透明・推測不能なトークン | データベースの主キー |
v7が引き受けるトレードオフ
v7が並べ替え可能なのは、まさに生成時刻を埋め込んでいるからであり、それが 同時に唯一の漏れでもあります。v7 UUIDを手にした誰もが、それがいつ生成された かをミリ秒単位まで読めます。主キーにとっては、たいてい無害でしばしば有用です。 タイミングを露出させたくない公開トークン、つまりクーポンコード、招待リンク、 列挙可能に近いものでは、その漏れはv4にとどまる理由になります。バージョンは、 そのIDが何にさらされるかを見て選ぶものであり、習慣で選ぶものではありません。
ULIDを見たことがあるなら、それはv7と同じ発想です。ミリ秒タイムスタンプの 接頭辞にランダム値を加えたもので、v7の標準化に先んじており、別のテキスト エンコーディング(Crockford base32、26文字)を使います。v7はRFCが認めた形式で、 通常のUUIDとして相互運用できるので、新規の作業ではULIDをそれより選ぶ理由は ほとんどありません。
読み取りと生成
UUIDを調べるときは、まずバージョンの数字を見て、それから残りが何を意味するかを
判断します。v7の先頭バイトはタイムスタンプにデコードされますが、v4のバイトは
何にもデコードされません。生成の面では、最新のランタイムが追いついてきています。
ブラウザとNodeのcrypto.randomUUID()は依然としてv4を出し、v7はライブラリ
(npmのuuid、他のエコシステムのuuid6/uuid7)を通じて、そしてますます
データベースで直接利用できます。私たちの
UUIDジェネレーターは両方のバージョンをブラウザで
生成するので、依存関係を取り込まずにひとまとめでコピーできます。
どちらのバージョンもできない一つのことは、何かを認証することです。UUIDは 識別子であって、検証できる秘密ではありません。あなたが発行したことを証明する トークンが必要なら、それは署名であり、 JWT署名アルゴリズムで扱っています。 そしてUUIDはハッシュではありません。コンテンツをフィンガープリント化しようと UUIDに手を伸ばしているなら、 ハッシュ・暗号化・エンコーディング の区別こそがあなたに必要なものです。
要約すると、主キーや大量に保存するものにはv7に手を伸ばし、IDが公開され、 生成時刻が誰の関心事でもあってはならないときにはv4を使います。