15年間、UUID v4が唯一の答えだった。ランダムな128ビット識別子で、ブラウザで生成し、データベースカラムにコピペし、あらゆる場所で使った。うまくいった。2024年にRFC 9562がv7を標準化し、議論は変わった。
短い答え:新しいデータベースプライマリキーにはv7を使い、セキュリティトークンとセッションIDにはv4を使え。 以下にその理由を説明する。
UUIDとは何か
UUIDは128ビットの識別子で、通常はダッシュで区切られた32文字の16進数で書かれる:550e8400-e29b-41d4-a716-446655440000。ダッシュは見た目だけだ。バイトが重要だ。128ビットは2¹²⁸個の可能な値を持つ — 約340セデシロンだ。使い切る心配はない。
バージョン番号(2番目のダッシュの後の最初の桁 — 550e8400-e29b-**4**1d4-a716-446655440000の4)はUUIDがどのように生成されたかを示す。これが重要な部分だ。
UUID v4:純粋なランダム
UUID v4は「UUID」と言ったとき、多くの人が意味するものだ。128ビットのうち122ビットがランダムで、残り6ビットがバージョンとバリアントをエンコードしている。ランダム性がすべてだ — 利用するパターンはなく、次のIDを推測する方法はなく、時間相関はない。
モダンブラウザで1つ生成:
crypto.randomUUID() // → "550e8400-e29b-41d4-a716-446655440000"
きわめてシンプルだ。しかし、明らかにデータベースには向かない。
問題はB-treeインデックスだ。B-treeはほとんどのインデックス(Postgres、MySQL、SQLite、MongoDB)の背後にあるデータ構造だ。データをソートされた状態に保ち、範囲クエリを高速にし、ディスク上のほぼ同じ場所に挿入する。ランダムなUUIDを挿入すると、データベースはツリーの途中に配置せざるを得ず、すべての挿入がランダムI/Oになる。数百万行になると、書き込みスループットが破壊される。
かつての修正は「UUID v4を使いつつ順序付けること」だった — しかしv4は定義上ランダムだ。人々は工夫した(MySQLのUUID_TO_BIN(..., 1)はタイムビットを前にスワップする)。RFC 9562はそのハックを標準にした。
UUID v7:時間順序
UUID v7はミリ秒精度のタイムスタンプを上位ビットに、ランダム性を下位ビットに置く:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms | ver | rand_a |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|var| rand_b |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| rand_b |
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
最初の48ビットはミリ秒のUnixタイムスタンプだ。次の4ビットはバージョン(7)。次に74ビットのランダム、2ビットのバリアント、さらに48ビットのランダムが続く。
結果:同じミリ秒に生成されたUUIDは挿入順にソートされる。後から生成されたUUIDは後になる。挿入はB-treeの右側に行き、途中ではない。書き込みスループットはデータベースによらず2〜10倍向上する。
重要なベンチマーク
PlanetScaleが2024年にベンチマークを公開し、数値を示した。MySQL 8でプライマリキーインデックスを使った場合:
| フォーマット | 挿入/秒 | 備考 |
|---|---|---|
| Auto-increment INT | 35,000 | 最速だが情報を漏洩する |
| UUID v7 | 18,000 | ほとんどのアプリでINTと同等 |
| UUID v4 | 4,500 | ランダム挿入 = ページ分割 |
| UUID v1(タイムスタンプ付き) | 14,000 | 古い時間順序バリアント |
v4とv7の4倍の差は、ランダムI/Oを実行するコストだ。高トラフィックサービスでは、これはそのまま金銭に直結する。
v4を使う場合
UUID v4はID自体がセキュリティの境界である場合にまだ正しい選択だ:
- セッショントークン — セッションIDが作成時刻を漏洩したくない場合。UUID v7は時間でソートされるため、1つのIDを知っている攻撃者は隣接IDを推測できる。(実用的な悪用可能性は低い — 74ビットのランダム性は依然として多い — しかし原則は重要だ。)
- パスワードリセットトークン — 同じ論理。
- 第三者に配布するAPIキー — IDが推測不可能であるべき場合、時間順序は望ましくない機能だ。
- CSPRNG出力として使用されるものすべて — v4は
crypto.randomUUID()が返すものだ。IDを暗号ランダム性に使う場合、v4が自然な適合だ。
v7を使う場合
UUID v7は以下にとっての正しいデフォルトだ:
- データベースプライマリキー — これがヘッドラインのユースケースだ。新しいスキーマのほとんどはv7を選ぶべきだ。
- イベントログID — 別のタイムスタンプカラムなしで時間でソート可能。
- メッセージキューのメッセージID — 一意性を持つFIFO順序。
- 分散システムの識別子 — シーケンスを協調せずに粗い順序が欲しい場合。
生成方法
ブラウザではv4が組み込みだ:
crypto.randomUUID() // v4
v7には小さなポリフィルが必要だ — crypto.getRandomValuesにタイムスタンプレフィックスを追加したもの。DevSpeedToolsのUUIDジェネレーターはブラウザでcrypto.getRandomValuesを直接使い、v4とv7の両方を生成する。何もインストールせずにIDをスキーマに貼り付けられる。
Node.js 22+では、組み込みのcrypto.randomUUID()はまだv4のみを返す。npmパッケージuuid(v9+)はv7()を追加した。Pythonではuuid.uuid7()が3.14(2025年6月リリース)で登場し、標準ライブラリドキュメントで推奨選択肢になった。Postgresではgen_random_uuid()関数はまだv4を返す — v7を取得するには、クライアントサイドで生成して渡す。MySQL 9(2024年リリース)はUUID_V7_BIN()をファーストクラス型として追加し、MariaDBは11.7で追従した。Cloudflare D1、PlanetScale、Supabaseは2026年すべての新しいプライマリキーカラムでデフォルトでv7を生成する。
v4からv7への移行
v4プライマリキーを持つ既存のテーブルがある場合、移行は不要だ。2つのバージョンは同じカラムで共存する(uuid型は両方を受け付ける)。パフォーマンスの改善は挿入にのみ関わり、新しい行を追加する場合のみ重要だ。最もシンプルな移行:新しい挿入にはv7を生成し始め、既存の行はv4のままにする。新しいv7行と若干ソート順がずれる(v4にはタイムスタンプがないため)が、インデックスは問題ない。
2026年の新しいスキーマでは、v7がデフォルトだ。4倍の書き込みスループット向上は本物で、仕様は安定し、すべての主要言語ランタイムにv7ジェネレーターがあり、マネージドデータベースプロバイダーは新しいプライマリキーにデフォルトでv7を使っている。推測不可能で時間相関のないIDが本当に必要な場合のみv4を使おう。