
2025/12/15 19:08
Avoid UUID Version 4 Primary Keys in Postgres
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary
PostgreSQL のランダム UUID v4 主キーは、単純な連番整数 ID と比べて性能とストレージに大きな問題を引き起こします。v4 値は非順序であるため、B‑Tree インデックスへの挿入がページ間に散らばり、頻繁なページ分割・断片化、大きめのリーフノード(約 40 % 大きい)、WAL I/O の増加、およびキャッシュヒット率の低下を招きます。研究では、時間順序付き UUID v7 または単純整数 ID へ切り替えることで WAL トラフィックがほぼ半減し、保守負荷も軽減できることが示されています。
UUID は PostgreSQL の
uuid 型で保存される 128‑bit バイナリ値です。RFC 4122 がそのフォーマットを定義していますが、v4 は高負荷下では一意性を保証しないと記載されています。UUID は bigint(8 バイト)に対して 16 バイトのストレージフットプリントを二倍にします。また RFC 4122 セクション 6 により 暗号学的またはセキュリティ目的には安全ではありません。
v4 の問題を緩和するには、テーブルの再パック(
pg_repack、pg_squeeze)や VACUUM FULL、REINDEX CONCURRENTLY の実行、大きなメモリバッファ(shared_buffers をデータベースサイズの約 4 倍に設定)と work_mem のチューニングが必要です。Rails 開発者は implicit_order_column を使用するか、順序付き列でクラスタリングして局所性を向上させることもできます。
新規データベースでは 32‑bit または bigint ID を既定に設定してください。v4 を使用している既存テーブルは、新しい行だけ v7 に切り替えることで全データの移行なしに対応可能です。PostgreSQL 18+ はネイティブ
pg_uuidv7 サポートを提供します。連番または時間順序付き ID を採用することで、スケーラビリティが向上し、書き込み遅延が低減し、大規模アプリケーションの運用コストも削減されます。本文
はじめに
過去10年間、UUID v4 を主キーとして使用するデータベースは、一般的にパフォーマンスの低下と過剰な I/O が問題となってきました。
UUID は PostgreSQL のネイティブ型で 16 バイト(128 ビット)で保存されます。
RFC 4122 では複数のバージョンが定義されており、Version 4 は主にランダムビットから構成されるため、生成時刻や場所を隠蔽します。
PostgreSQL(v13 以降)では
gen_random_uuid() により UUID v4 を生成できます。「UUID は安全だ」という誤解もありますが、多くのユーザーは理由に合わないケースでこの型を選択しています。
主張
UUID v4 を主キーとして使用しないこと。
一般的には、正当な代替手段が存在しない限り UUID の利用は避けるべきです。
本投稿の UUID コンテキスト
- UUID(Microsoft 用語では GUID)は 36 文字の文字列:32 桁の16進数+4 つのハイフンで構成され、128 ビットのバイナリ値として保存されます。
- RFC 4122 は 128 ビットがどのように設定されるかを規定しています。
- UUID v4 の値はほぼランダムです。UUID v7 は最初の 48 ビットにタイムスタンプを埋め込み、インデックス性能を向上させます。
- UUID v7 は PostgreSQL 18(2025 年秋予定)で採用される予定です。
ウェブアプリケーションへの適用範囲
- PostgreSQL を主な OLTP データベースとするモノリシックウェブアプリ(例:ソーシャルメディア、e‑commerce、クリック追跡、業務プロセス自動化)。
- これらのシナリオでは、非効率的なストレージと取得がパフォーマンス問題を引き起こします。
UUID v4 の核心的課題
ランダム性はインデックス挙動を悪化させます:
- 挿入遅延 – ランダム値は B‑tree 内の任意の位置に配置され、ページ分割や再バランスが整数順序より頻繁に発生します。
- 検索コスト – 更新・削除・範囲検索で非連続ページをたどる必要があり、I/O が増大します。
- 自然順序の欠如 – キャッシュ効率が低下します。
UUID が有用なケース
- クライアント側生成 – 複数サービスやデータベースで ID を生成し、衝突を避ける必要がある場合。
- 分散システム – シャード間で調整なしに一意の ID が必要なとき。
- 衝突回避 – ランダム v4 UUID は極めて低い衝突確率(≈ 2.71×10¹⁸ 前に 50 % の衝突確率)を持ちます。
RFC 4121 第6節: 「UUID が推測しづらいとは仮定せず、セキュリティ機能として使用してはならない。」
匿名 ID 用の代替策
ランダム UUID の代わりに整数から擬似乱数コードを生成できます:
-- 整数 → バイナリ → キーで XOR → Base62 エンコード
これにより外部に公開される非連続 ID を得つつ、内部キーは整数のままです。
UUID v4 の欠点
| 問題 | 説明 |
|---|---|
| 空間消費 | 16 バイト対 の 8 バイト。インデックスとテーブルサイズが倍増します。 |
| 挿入遅延・断片化 | ランダム配置により頻繁にページ分割、WAL I/O が増加。 |
| キャッシュヒット率低下 | ランダムページはバッファミスを多くし、shared_buffers に収まるページ数が減少します。 |
| 検索時の I/O 増大 | B‑tree のリーフ密度が低い(約 79 % 対整数で 98 %)。 |
実証データ
-
バッファヒット
インデックス – 27,332 ヒット。bigint- UUID v4 インデックス – 8,562,960 ヒット。
→ 約 850 万ページ(約 68 GB)がメモリからアクセスされ、10M 行・1M 更新で 1–3.4 秒の遅延を追加。
-
リーフページ密度 (
使用):pg_pageinspect
| インデックス | 平均リーフ充填率 |
|---|---|
| 97.64 |
| 79.06 |
| 90.09 |
緩和策
- インデックス再構築 –
、REINDEX CONCURRENTLY
、またはpg_repack
を使用。VACUUM FULL - 十分なメモリ確保 – データベースサイズの 4 倍を RAM に割り当てる(例:25 GB DB → 128 GB インスタンス)。
の増加 – ソートが多いクエリに対して。work_mem- Rails:
を利用し、UUID ではなく高カードなインデックス付きフィールドで並び替え。implicit_order_column - クラスタリング – 可能なら
のような順序付けられた列でクラスタ化。created_at
推奨事項
- 新規データベース – 小規模アプリは符号付き 32 ビット (
)、大規模または成長重視のアプリはint4
(bigint
) を使用。int8 - 既存 UUID v4 テーブル – 移行が可能なら時間順序付けられた UUID(例:UUID v7)への移行を検討。
- UUID が必須の場合 – 順序付け可能なバージョン(UUID v7 など)を選び、
を主キーに使わない。gen_random_uuid()
要約
- UUID v4 はランダム性により検索遅延と I/O が増大。
の2倍の空間を消費。bigint- RFC 4121 で「安全ではない」と明記。
- ランダム UUID はキャッシュヒット率低下・WAL トラフィック増加を招く。
- 匿名 ID を求めるなら整数から擬似乱数コードを生成。
- UUID が不可欠な場合は時間順序付けられたバージョン(UUID v7)を使用。
さらに読む
- Franck Pachot – “UUID in PostgreSQL” (AWS Heroes)。
- Brandur – “Identity Crisis: Sequence v. UUID as Primary Key”。
- 5 Minutes of PostgreSQL – “UUIDs vs Serial for Primary Keys”。
- andyatkinson/pg_scripts PR #20。
更新
- 2025‑12‑15 – Hacker News フロントページに掲載。
「ランダム性が問題」というセクションを明確化して更新しました。