UUID v7 と時刻順キー — 主キーに使う利点と落とし穴

UUID v7 は、先頭 48 ビットにミリ秒精度の Unix 時刻を置く UUID です。値そのものがおおむね生成順に並ぶため、データベースの主キーのように「順番に増えてほしい」場所で使われます。本記事では、なぜランダムな v4 を主キーにすると不利になるのか、v7 が何を解決するのか、そして導入前に知っておくべき落とし穴を整理します。

結論を先に:主キーやログの並び順が問題になっているなら v7、それ以外の一般的な一意識別子なら v4 で十分です。v7 は「時刻が読める」「同一ミリ秒内の順序は保証されない」という2点を許容できる場合にだけ選んでください。

1. v7 のビット配置 — 何が時刻順を作っているのか

UUID は 128 ビットの値です。v7 ではその先頭 48 ビットに Unix エポックからのミリ秒が入ります。続いて 4 ビットのバージョン(7)、2 ビットのバリアント、残りが乱数です。

領域ビット数中身
unix_ts_ms48Unix エポックからの経過ミリ秒
ver4バージョン(v7 なので 7)
rand_a / rand_b74乱数(一部は単調性のために使うことができる)
var2バリアント

16 進数表記は v4 と同じ 8-4-4-4-12 の 36 文字です。先頭が時刻なので、文字列としてソートしても時系列にほぼ一致する点が実務上いちばん効きます。バイト列で比較しても同じ順序になります。

UUID の仕様は RFC 4122 が長く使われてきましたが、2024 年の RFC 9562 がこれを置き換え、v6・v7・v8 を追加しました。v7 はこの新しい仕様で定義されたものです。

2. なぜ v4 を主キーにすると不利になるのか

v4 は 122 ビットが乱数で、値の並びに規則性がありません。これが主キーになると、多くの RDBMS が採用する B-Tree インデックスとの相性で不利になります。

一方、時刻順に増えるキーなら挿入は常にインデックスの末尾付近に集まります。触るページが局所化するのでキャッシュが効き、分割も末尾でのみ起きます。v7 が解決しているのはこの一点です。

ただし、これは規模が出て初めて差になる話でもあります。数万行のテーブルで v4 から v7 へ移す価値はほとんどありません。移行はスキーマと既存データに影響するので、必ず自分の環境で計測してから判断してください。

3. 落とし穴1: 生成時刻が読める

v7 の先頭 48 ビットは加工されていないミリ秒時刻です。つまり ID を見れば、そのレコードがいつ作られたかが分かります。16 進数の先頭 12 桁を 10 進に直すだけで復元できます。

連番の整数 ID のように「全体で何件あるか」が漏れることはありませんが、時刻そのものが業務上の機密になる場面はあります。たとえば申込の受付時刻、問い合わせの発生時刻、内部処理のタイミングなどです。公開する ID と内部の主キーを分けるのが素直な対処になります。

4. 落とし穴2: 同一ミリ秒内の順序は保証されない

時刻が同じ 1 ミリ秒の中では、後ろの乱数によって順序が決まります。つまり同一ミリ秒に生成した値どうしの並びはランダムです。高頻度で生成する処理では、これが「作成順に並ばない」形で表面化します。

RFC 9562 は、乱数領域の一部をカウンタとして使う単調性の追加方式を示しています。厳密な順序が要るなら、その方式に対応した実装を選ぶか、順序専用の列(シーケンスや挿入時刻)を別に持たせてください。「v7 だから作成順に並ぶ」と仕様書のように扱わないことが要点です。

5. 落とし穴3: 時計に依存する

v7 の値は生成した機械の時計を反映します。したがって、時計がずれているホストが混ざると順序も崩れます。NTP による時刻同期が前提になりますし、時計が巻き戻ったときの挙動は実装によって異なります(同じミリ秒に留める、カウンタで吸収するなど)。

複数のホストで採番する構成では、ホスト間の時計のずれがそのまま順序のずれになります。ミリ秒単位の正確な順序を業務要件にしている場合は、UUID ではなく中央で採番する仕組みを検討したほうが確実です。

6. v4 / v7 / ULID の使い分け

UUID v4UUID v7ULID
並び順ランダムほぼ時刻順ほぼ時刻順
表記36文字の16進36文字の16進26文字(Crockford Base32)
時刻の露出無しあり(ミリ秒)あり(ミリ秒)
標準RFC 4122 / 9562RFC 9562仕様は公開だがRFCではない

どれを選ぶ場合も、乱数部分は暗号論的に安全な乱数(CSPRNG)で作ることが前提です。ブラウザなら crypto.getRandomValues()、Node.js なら crypto モジュールが該当します。Math.random() は予測可能なので使ってはいけません。

TOOL UUID生成(v4 / v7) v4 と v7 を切り替えてまとめて生成し、そのままコピーできます。ブラウザ内で完結し、値は送信されません。

よくある質問(FAQ)

UUID v7 は必ず時刻順に並びますか?

同じミリ秒の中では保証されません。v7 が時刻順に並ぶのは先頭48ビットのミリ秒タイムスタンプによるもので、その後ろは乱数です。したがって、同一ミリ秒に生成した複数の値の並びはランダムになります。ミリ秒単位より細かい順序が必要な場合は、RFC 9562 が示す単調性の追加方式を実装するか、別途シーケンス列を持たせてください。

v4 の主キーは本当に性能問題になりますか?

テーブルの規模と挿入頻度によります。B-Tree インデックスは近い値をまとめて格納するため、挿入キーがランダムだと書き込み位置がインデックス全体に散らばり、キャッシュに乗りにくくなってページ分割も増えます。小規模なテーブルでは体感できませんが、行数が増えるほど差が出ます。まず自分の環境で計測し、問題が確認できてから移行するのが妥当です。

UUID v7 を公開APIのIDに使っても安全ですか?

生成時刻が読み取れることを許容できるかで判断してください。v7 の先頭48ビットはミリ秒のUnix時刻そのもので、ID を見た第三者がレコードの作成時刻を復元できます。連番のように件数を推測されることはありませんが、時刻が業務上の機密になる場合(申込の受付時刻など)は、公開用に別の識別子を持たせるほうが安全です。

← 技術ブログ一覧へ戻る