エンジニアリング · 2026年8月28日

UUID v4 vs v7 — 2026年にどれを選ぶべきか

UUID v7はデータベースプライマリキーの新しいデフォルトですが、v4はセキュリティトークンにまだ正しい選択です。選び方、サイドバイサイド比較とベンチマーク。

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を使おう。