[UUIDv7 vs UUIDv4] 違いをまとめてみた|使い分けの特徴やDBパフォーマンス差の比較
この記事でわかること
- UUID v4とv7の5つの違い(生成方式・性能・プライバシー等)
- なぜv7はデータベース性能向上が見込まれるのか
- v4とv7の使い分けの判断基準と具体的な選択フロー
- Go/PostgreSQLでの実装コード例
対象読者
- UUID v4とv7のどちらを選ぶべきか悩んでいる方
- v4とv7の違いを技術的に理解したい方
- データベースのインデックス性能を改善したい方
- 分散システムでのID設計に取り組むエンジニア
記事を書こうと思ったきっかけ
2024年5月、UUIDの新仕様RFC 9562が正式に標準化され、新たにUUID v7が追加されました。これにより、従来のUUID v4が抱えていたデータベース性能問題を解決する選択肢が生まれました。
本記事では、v4とv7の違いに焦点を当て、公式仕様に基づいて生成方式の違い、性能差の理由、使い分けの判断基準を実装例を交えながら詳しく解説します。
UUID v4とv7の違い|5つの観点で比較
まず結論から。UUID v4とv7の最大の違いは「生成方式」 です。v4は完全ランダム、v7はタイムスタンプ+ランダムという構造の違いから、性能やプライバシー特性が大きく異なります。
一目でわかる比較表
| 比較項目 | UUID v4 | UUID v7 | 優位性 |
|---|---|---|---|
| ① 生成方式 | 完全ランダム(122bit) | タイムスタンプ(48bit) + ランダム(74bit) | - |
| ② 時系列ソート | ❌ 不可 | ✅ 可能 | v7 |
| ③ DB INSERT性能 | 低い(ランダム挿入) | 高い(順次挿入) | v7 |
| ④ プライバシー | 高い(生成時刻不明) | やや低い(生成時刻が含まれる) | v4 |
| ⑤ デバッグ性 | 難(時系列不明) | 易(時系列追跡可) | v7 |
| 標準化 | RFC 4122(2005年) | RFC 9562(2024年) | - |
| エコシステム | 完全対応 | 拡大中 | v4 |
使い分けの判断基準
【選択フローチャート】
プライバシーが最優先?
├─ YES → UUID v4
│ (例:ユーザーID、セッションID、APIキー)
│
└─ NO → 性能・スケーラビリティが重要?
├─ YES → UUID v7
│ (例:注文ID、ログID、イベントID)
│
└─ どちらでもない → 既存システムならv4継続
新規ならv7を検討
それでは、それぞれの詳細を見ていきましょう。
UUIDとは?(前提知識)
UUID(Universally Unique Identifier)は、128ビット(16バイト)の識別子で、中央管理サーバーなしに世界中で一意なIDを生成できる仕組みです。
550e8400-e29b-41d4-a716-446655440000
(8-4-4-4-12桁のハイフン区切り、36文字)
分散システムで異なるサーバーが同時にIDを生成しても衝突しない特性から広く採用されています。
参照:RFC 9562 - Universally Unique IDentifiers (UUIDs)
UUID v4の詳細解説|完全ランダム生成の仕組み
UUID v4の仕組み
UUID v4は122ビットの完全ランダム値で構成されます(残り6ビットはバージョンとバリアント識別用)。
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
↑ ↑
| └─ バリアント
└────── バージョン(4を示す)
生成アルゴリズム:
- 128ビットのランダム値を生成
- バージョンフィールドを
4に設定 - バリアントフィールドをRFC 4122準拠の値に設定
メリット
1. 実装が極めて簡単
暗号学的に安全な乱数生成器さえあれば実装可能です。
Go言語での実装例:
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// UUID v4生成
id := uuid.New()
fmt.Println(id.String())
// 出力例: 3d6f9e42-8a5b-4c7d-9e2f-1a3b5c7d9e0f
}
PostgreSQLでの実装例:
-- gen_random_uuid()関数を使用(PostgreSQL 13以降)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO users (name) VALUES ('Alice');
-- idは自動生成される
2. プライバシー保護
タイムスタンプやMACアドレスを含まないため、生成時刻や生成元マシンの情報が漏洩しません。
3. エコシステムの成熟
ほぼすべてのプログラミング言語、データベース、フレームワークがネイティブサポートしています。
デメリット
1. データベースインデックスの断片化
最大の課題は、ランダムな値のため、連続して挿入してもソート順がバラバラになることです。
挿入順序: ID(v4)
1番目 → f47ac10b-58cc-4372-a567-0e02b2c3d479
2番目 → 3d6f9e42-8a5b-4c7d-9e2f-1a3b5c7d9e0f
3番目 → 7c9e6679-7425-40de-944b-e07fc1f90ae7
↑ ソート順序がバラバラ
B-Treeインデックスへの影響:
- ページスプリットが頻発
- キャッシュヒット率の低下
- INSERT性能の劣化(特に大量データ時)
ベンチマーク結果(参考値):
- PostgreSQLでの100万件INSERT
- UUID v4: 約15秒
- UUID v7: 約8秒(後述)
- 連番ID: 約5秒
2. デバッグの困難さ
生成時刻が含まれないため、ログから時系列を追うのが難しくなります。
UUID v4を使うべきケース
以下の条件に当てはまる場合、UUID v4が最適です:
✅ プライバシーが最優先(生成時刻を隠したい)
✅ データ量が比較的少ない(〜数百万レコード)
✅ INSERT性能がクリティカルでない
✅ 既存システムとの互換性が必要
UUID v7の詳細解説|タイムスタンプベース生成の仕組み
UUID v7の仕組み
UUID v7は先頭にタイムスタンプ、その他にランダム値という構造を持ちます。
UUID v7の例:
01890a5d-ac96-7000-8000-123456789abc
↑↑↑↑↑↑↑↑ ↑↑↑↑ ↑ ↑
タイムスタンプ v7 ランダム値
構成:
-
先頭48ビット: 生成時刻(UNIXエポックからのミリ秒)
- 例: 2024年1月1日 12:34:56.789
-
残り74ビット: ランダム値
- 同一ミリ秒内での衝突を防ぐ
- その他: バージョン(4bit)とバリアント(2bit)の識別情報
この構造により、時間が経過するとUUIDの値も増加するため、生成順序=ソート順序となります。
メリット
1. 時系列ソートが可能に(最大の利点)
タイムスタンプが先頭にあるため、生成順序=ソート順序になります。
挿入順序: ID(v7)
1番目 → 01890a5d-ac96-7000-8000-123456789abc
2番目 → 01890a5d-ac97-7000-8000-234567890def
3番目 → 01890a5d-ac98-7000-8000-345678901fed
↑ 時系列順にソートされる
2. データベース性能の向上
UUID v7を使用することで、様々な側面でデータベースのパフォーマンスが向上します。主な要因として以下の2つがあります:
- ページスプリットの減少によるパフォーマンス向上
- B-Treeインデックス作成時のキャッシュミスの減少によるパフォーマンス向上
B-Treeインデックスの基礎知識
B-Treeの構造(簡略化):
[50, 100] ← 中間ノード
/ | \
[1-49] [50-99] [100-150] ← リーフページ
- データは「ソート順」でリーフページに格納される
- 1ページに収まる件数には限界がある(例:数百〜数千件)
- ページが満杯になると「ページスプリット」が発生
- 頻繁にアクセスされるページはメモリ(バッファプール)にキャッシュされる
性能向上要因① ページスプリットの減少
UUID v4での問題(ランダム挿入):
既存データ: [10] [20] [30] [40] [50]
↓
新規UUID: [25] ← ランダムな値
↓
挿入位置: [10] [20] ★[25]★ [30] [40] [50]
↑
中間のページに挿入する必要がある
問題点:
- ランダムな値のため、既存データの「途中」に挿入される
- ページが満杯だと「ページスプリット」が頻発
- データ移動 + ページ再編成 + インデックス更新のコストが発生
UUID v7での改善(順次挿入):
既存データ: [01890a5d-1000] [01890a5d-2000] [01890a5d-3000]
↓
新規UUID: [01890a5d-4000] ← タイムスタンプベースで常に最大値
↓
挿入位置: [01890a5d-1000] [01890a5d-2000] [01890a5d-3000] ★[01890a5d-4000]★
↑
常に「末尾」に追加される
利点:
- タイムスタンプが常に増加するため、「末尾追加」になる
- ページスプリットが最小限(最後のページのみ)
- データ移動や再編成のコストが大幅に削減
性能向上要因② キャッシュミスの減少
UUID v4での問題(ランダムアクセス):
大量挿入時の動作:
挿入1: ページ [A] を読み込み → キャッシュに格納
挿入2: ページ [K] を読み込み → キャッシュに格納
挿入3: ページ [C] を読み込み → キャッシュに格納
...
挿入1000: ページ [D] を読み込み
→ キャッシュ満杯
→ 古いページを追い出し(キャッシュミス)
→ ディスクI/O発生 ⚠️
問題点:
- インデックス全体に分散してアクセス
- バッファプールが頻繁に入れ替わる
- ワーキングセット = インデックス全体
- キャッシュミス率が高く、ディスクI/Oが多発
UUID v7での改善(局所アクセス):
大量挿入時の動作:
挿入1: ページ [末尾] を読み込み → キャッシュに格納
挿入2: ページ [末尾] に追記(既にキャッシュ内)✅
挿入3: ページ [末尾] に追記(既にキャッシュ内)✅
...
挿入1000: ページ [末尾] に追記(既にキャッシュ内)✅
利点:
- 常に同じページ(末尾付近の数ページ)にのみアクセス
- ワーキングセット = 数ページのみ
- キャッシュミスがほぼ発生しない
- ディスクI/Oが劇的に削減
このデータ局所性の向上が、UUID v7の最大のパフォーマンス改善要因です。
性能差の実測例
実測報告:
Sansan社の事例(2024年12月報告):
環境: PostgreSQL、100万件のINSERT + PRIMARY KEY制約
UUID v4:
- キャッシュヒット率: 約60%
- ページスプリット多発
- 全ページが断片化し、ディスクI/O増加
UUID v7:
- キャッシュヒット率: 約93%に向上 ⭐
- ページスプリット最小限
- データが連続配置され、キャッシュ効率向上
結果: INSERT性能が大幅に改善
キャッシュヒット率が60%→93%に向上したということは、ディスクI/Oが約6分の1に削減されたことを意味します。
その他の実測報告:
- Sansan社: キャッシュヒット率が60%→93%に向上(2024年12月報告)
- Future社: PostgreSQL 18で約20%の時間短縮を確認(2024年報告)
注意点:
- 性能向上率は環境・データ量・ワークロードにより変動
- 大規模データ(数千万〜億件)ほど差が顕著
- 特に大量レコードの一括挿入時にキャッシュ効率の差が際立つ
3. タイムスタンプの透明性
IDから生成時刻を抽出でき、デバッグやログ分析が容易になります。
Go言語での実装例:
package main
import (
"fmt"
"time"
"github.com/google/uuid"
)
func main() {
// UUID v7生成
id, _ := uuid.NewV7()
fmt.Println("UUID v7:", id.String())
// タイムスタンプ抽出
timestamp := extractTimestamp(id)
fmt.Println("生成時刻:", timestamp)
}
func extractTimestamp(id uuid.UUID) time.Time {
// 先頭48bitからミリ秒を抽出
ms := int64(id[0])<<40 | int64(id[1])<<32 |
int64(id[2])<<24 | int64(id[3])<<16 |
int64(id[4])<<8 | int64(id[5])
return time.UnixMilli(ms)
}
4. 衝突確率の低さ
同一ミリ秒内でも74ビットのランダム性により、実用上衝突はほぼ発生しません。
デメリット
1. タイムスタンプから生成時刻が特定できる
先頭48ビットにミリ秒単位のタイムスタンプが含まれるため、IDから生成時刻が特定できます。RFC 9562のSecurity Considerationsでも、この点がプライバシーやセキュリティ上の考慮事項として明記されています。
影響:
- IDから生成時刻が推測される
- 生成順序が完全に把握される
対策:
- ユーザーIDなど公開されるIDにはUUID v4を使用
- 内部的なリソースID(注文ID、ログIDなど)にはUUID v7を使用
2. クロック同期の必要性
分散システムでは、サーバー間でクロックが同期していないと順序保証が崩れる可能性があります。
UUID v7を使うべきケース
以下の条件に当てはまる場合、UUID v7が最適です:
⭐ 大量データを扱う(数百万〜数億レコード)
⭐ INSERT性能がクリティカル
⭐ 時系列でのソートが必要
⭐ ログやイベントトラッキング
⭐ 新規プロジェクト
まとめ
v4とv7の違い|重要ポイント5つ
① 生成方式の違い
- v4: 122ビットの完全ランダム値
- v7: 48ビットのタイムスタンプ + 74ビットのランダム値
この構造の違いが、以下すべての特性差を生み出しています。
② データベース性能の違い
- v4: ランダム挿入でページスプリット多発 → 性能低
- v7: 順次挿入でインデックス効率化 → 性能向上
実測では20〜47%の改善例あり(環境・データ量により変動)。大量データ(数百万〜数億レコード)を扱う場合、v7が有利。
③ プライバシー特性の違い
- v4: 生成時刻が不明 → プライバシー保護に優れる
- v7: 先頭48ビットに生成時刻が含まれる → プライバシー性がやや低い
RFC 9562では、v7のタイムスタンプから生成時刻を特定できる点について明記されています。ユーザーIDなど公開されるIDにはv4が適しています。
参照:RFC 9562 Section 6.1 - Security Considerations
④ デバッグ性の違い
- v4: 時系列追跡が困難
- v7: IDから生成時刻を抽出可能 → ログ分析が容易
⑤ エコシステムの成熟度
- v4: 完全対応(2005年標準化)
- v7: 拡大中(2024年標準化)
既存システムならv4継続、新規ならv7検討が妥当。
Discussion