UUID v4 vs UUID v7徹底比較:B-TreeインデックスとDBキー設計の最適化
RFC 9562新標準UUIDv7が、なぜPostgreSQLやMySQLのB-Treeインデックス断片化を防ぎ、大量インサート時の書き込みスループットを劇的に改善するのかをアーキテクチャ視点で解説します。
UUID 生成ツール
データをサーバーに送信せず、すべてブラウザ内で安全に検証・実行できます。
分散システムの主キー(Primary Key)としてのUUIDv4の功罪
UUID(Universally Unique Identifier)は、中央の発行機関やデータベースへの問い合わせなしに、世界中で重複のない一意な値を生成できる128ビットの識別子です。その中でもUUIDv4は、122ビットの完全な暗号学的乱数で構成されており、フロントエンドや分散マイクロサービスでIDを事前採番できる柔軟性から広く愛用されてきました。
しかし、UUIDv4をリレーショナルデータベース(PostgreSQLやMySQL InnoDBなど)のクラスタ化主キー(Clustered Primary Key)に採用すると、テーブルのデータ件数が数百万〜数千万件規模へ成長するにつれて、インサート性能に深刻なボトルネックが発生することが知られています。
完全ランダムなUUIDv4がB-Treeインデックスを破壊するメカニズム
リレーショナルデータベースの主キーは、一般にB-Tree(B+Tree)インデックス構造でディスク上に管理されています:
- 連番整数(AUTO_INCREMENT / BIGSERIAL)の場合: 新しい行のキーは常に直前の値より大きいため、B-Treeの最右端のリーフノードに順番に追加されます。ページがいっぱいになれば新しいページを末尾に割り当てるだけで済むため、ページの充填率(充填密度)が高く保たれます。
- UUIDv4(完全ランダム)の場合: 値が完全に不規則に散らばるため、B-Treeのあらゆるリーフページの中間にランダムに割り込み挿入されます。すでに満杯になっている中間ページにデータが入ると、既存のディスクブロックを強制的に2つに分割するページ分割(Page Split)が頻発します。
- インデックス全体のサイズがサーバーのメモリ(RAM / Buffer Pool)を超過した途端、1回のインサートごとにディスクからランダムなページを読み込み、ダーティページを書き出すランダムI/Oが激増し、書き込み性能が大幅に低下してしまいます。
RFC 9562新標準UUIDv7の内部構造:時間順ソートの魔法
2024年に承認されたRFC 9562標準のUUIDv7は、従来の乱数UUIDが持つ「分散環境での衝突耐性」と、連番整数が持つ「B-Treeインデックス親和性」を見事に融合させた画期的な仕様です:
- 上位48ビット: ミリ秒精度のUnixエポックタイムスタンプがビッグエンディアン形式で格納されます(西暦10889年までオーバーフローしません)。
- 中間4ビット: UUIDバージョン番号(
0111、すなわちバージョン7)。 - 中間12ビット(
rand_a): ミリ秒未満の順序整合性を保つためのシーケンスカウンタまたはサブミリ秒フラクション(同一ミリ秒内で連続生成された場合でも順序が崩れません)。 - 続く2ビット: UUIDバリアント(標準の
10)。 - 下位62ビット(
rand_b): 暗号学的に安全な乱数バイト。
UUIDv7は生成された時刻順に自然ソートされるため、データベース挿入時に常に最新のリーフページの末尾付近へ順次追加され、ページ分割を劇的に削減します。
主要な言語およびデータベースでのUUIDv7導入ガイド
- PostgreSQL: PostgreSQL 18からネイティブ関数
uuidv7()が標準搭載されました。PostgreSQL 17以下の環境では、広く使われているpg_uuidv7拡張機能のuuid_generate_v7()を使用できます。 - Node.js / TypeScript: 公式の
uuidパッケージ(v10以降)でv7()を呼び出すことでミリ秒順序のUUIDv7を即座に生成できます。 - Python: Python 3.14以降で標準ライブラリの
uuid.uuid7()が組み込まれました。 - DevUtilsのUUID Generatorツールを使用すれば、ブラウザ上で単一および大量のUUIDv4/UUIDv7を瞬時に生成・比較検証できます。
-- PostgreSQL 18以降でのネイティブUUIDv7テーブル定義例
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID NOT NULL,
total_amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
);
-- B-Treeインデックスの断片化が抑えられ、大量インサート時も安定したI/Oを維持無停止マイグレーション戦略:UUIDv4からUUIDv7への安全な移行手順
既存の大規模な本番データベースをUUIDv4からUUIDv7へ移行する際は、テーブル全体のロックを防ぐ無停止マイグレーション手順を踏むことが不可欠です:
- シャドウカラム(Shadow Column)の追加: 既存の
idカラムとは別に、NULLを許容する新しいid_v7カラムを追加します。 - アプリケーション層でのデュアルライト(Dual-Write): 新規インサート時にUUIDv7を採番して
id_v7に書き込む一方、参照クエリは従来のidを使用し続けます。 - 非同期バックフィル(Backfill): トラフィックの少ない時間帯にバックグラウンドジョブを実行し、既存レコードの作成日時(
created_at)を基準にUUIDv7を決定論的に生成して補完します。 - コンカレントインデックス構築: PostgreSQLの
CREATE UNIQUE INDEX CONCURRENTLYを使用して、書き込みをブロックすることなくid_v7にユニークインデックスを作成します。 - 制約の切り替えと完了: 短いトランザクション内で主キー制約を
id_v7に切り替え、旧カラムを段階的に廃止します。
-- PostgreSQLでの無停止UUIDv7移行パターン
ALTER TABLE orders ADD COLUMN id_v7 UUID DEFAULT uuidv7();
-- 既存データの非同期バックフィル(書き込みロックを防止)
UPDATE orders SET id_v7 = uuidv7() WHERE id_v7 IS NULL;
-- 読み書きを停止せずに安全にユニークインデックスを構築
CREATE UNIQUE INDEX CONCURRENTLY idx_orders_id_v7 ON orders(id_v7);UUID 生成ツール
無料のオンラインUUID v4生成ツール。ブラウザ内で高速・安全に生成。