Article
PostgreSQL 18のUUIDv7を主キーに選ぶ理由と注意点
PostgreSQL 18は、時刻順に並べやすいUUIDを生成するuuidv7()を標準搭載しました。UUIDv4とのB-treeインデックスの違い、時刻情報が見えるというトレードオフ、既存テーブルでの段階的な切り替え方を整理します。
Share
こはるの読みどころ
UUIDv7は「UUIDが必要で、挿入の局所性も高めたい」場面の有力候補だよ。公開IDに含まれる時刻を見せてよいか、既存データをどう扱うかまで一緒に考えてみよう!

PostgreSQL 18には、時刻順に並べやすいUUIDをデータベース内で生成するuuidv7()が加わりました。PostgreSQL 18のリリースノートにも主要機能として挙げられています。
分散した場所でIDを生成できるUUIDは便利ですが、ランダムなUUIDv4を主キーにすると、B-treeの広い範囲へ書き込みが散ります。では、UUIDv7へ替えれば主キーの悩みは解決するのでしょうか。
2026年8月21日に公開された検証では、100万行を挿入したUUIDv7のインデックスがUUIDv4より小さく、密に保たれました。その理由と、時刻情報の露出や既存データとの混在まで追うと、UUIDv7を選ぶ条件が見えてきます。
UUIDv7の時刻ビットがB-treeの挿入位置を右端へ寄せる
UUIDv4は、バージョンとバリアントを除くビットをランダム値として使います。値の大小もランダムになるため、主キーの新しいエントリはB-treeのさまざまなリーフページへ入ります。
UUIDv7は配置が異なります。RFC 9562では、先頭48ビットにミリ秒単位のUnix時刻を置き、バージョンとバリアントを除く残り74ビットをランダム値や単調性を補う情報に使えると定めています。新しい値ほど先頭部分が大きくなりやすいため、昇順のB-treeでは挿入位置が右端付近へ集まります。
PostgreSQL 18の実装は、ミリ秒の後ろに12ビットのサブミリ秒情報を置き、残りをランダム値にしています。UUID関数の公式ドキュメントには「ミリ秒時刻+サブミリ秒時刻+ランダム」と説明され、実装追加時のコミット記録では同一バックエンド内の単調性を確保する設計も示されています。
新しいテーブルなら、使い方はシンプルです。
CREATE TABLE event_log (
id uuid PRIMARY KEY DEFAULT uuidv7(),
created_at timestamptz NOT NULL DEFAULT now(),
payload jsonb NOT NULL
);
uuid型や主キー制約はそのままで、既定値をuuidv7()にします。PostgreSQLはUUIDの比較演算も提供するため、時刻が先頭にあることがインデックス上の並びへそのまま効いてきます。
100万行の比較ではUUIDv7インデックスが38MBから30MBへ縮小
公開された検証では、gen_random_uuid()を使うテーブルとuuidv7()を使うテーブルへ、それぞれ同じペイロードを100万行挿入しています。主キーインデックスの結果は次のとおりでした。
| 生成関数 | インデックスサイズ | 平均リーフ密度 | リーフ断片化 |
|---|---|---|---|
gen_random_uuid() |
38 MB | 71.53 | 49.89 |
uuidv7() |
30 MB | 89.98 | 0.00 |
UUIDv7側は近い値が連続して入り、現在触っているリーフページを埋めてから次へ進めます。一方、UUIDv4側は挿入位置が散り、満杯に近いページの途中へ値を入れるためのページ分割が起きやすくなります。PostgreSQLのB-tree解説でも、B-treeがデータ型のソート順を持ち、ページ分割が新しいページと親へのリンクを作る処理であることを確認できます。
平均リーフ密度とリーフ断片化は、pgstattuple拡張のpgstatindex()が返す指標です。公式ドキュメントが説明するように、値はページごとに集計されるスナップショットであり、インデックスサイズと同じくワークロードや計測時点に左右されます。ここでの38 MBと30 MBは仕組みを理解する実測例であって、すべての環境に同じ差を約束する数値ではありません。
それでも、UUIDのランダム性がB-treeの書き込み位置を変え、ページ密度とキャッシュ効率へつながる因果関係は押さえておきたいところです。UUIDが必要な書き込み中心のテーブルほど、自分のデータ分布で比較する価値があります。
時系列で並ぶUUIDは作成時刻を隠さず、全ノードの厳密順序も保証しない
並べやすさは、情報を持つことの裏返しです。UUIDv7の先頭48ビットからはミリ秒単位の時刻を読み取れ、PostgreSQL 18にはuuid_extract_timestamp()もあります。ただし公式ドキュメントは、抽出した時刻が実際の生成時刻と常に完全一致するとは限らず、生成実装に依存すると注意しています。
公開APIのIDから作成順やおおよその時刻が分かると困る場合、UUIDv7は無条件の置き換えになりません。RFC 9562のセキュリティ考慮事項も、UUIDを推測困難と決めつけず、所持だけでアクセスを許すセキュリティ機能に使わないよう求めています。時刻を含むUUIDをセキュリティ操作に関係させる必要がある場合はUUIDv4を使うよう勧めています。
順序性の範囲にも注意が必要です。PostgreSQL実装が明示している単調性の保証は同一バックエンド内です。複数バックエンドや複数ノードで同時に生成した値を、業務イベントの厳密な全順序として扱う保証ではありません。
created_atを省けるように見えても、業務上の発生時刻、更新可能な時刻、監査時刻を表すなら独立した列を残す方が意味を明確にできます。UUIDv7の時刻は、主キーの局所性や大まかな並びに使う情報と考えるのが安全です。
PostgreSQL 18への移行は既存UUIDを書き換えずに始められる
uuidv7()がコア機能として使えるのはPostgreSQL 18以降です。gen_random_uuid()はPostgreSQL 13で拡張なしに使えるようになったため、17以前の環境で同じ関数名だけを置き換えることはできません。
2026年8月22日時点で、PostgreSQL 18はサポート中の現行メジャー版で、最新マイナー版は18.6です。公式バージョンポリシーは、メジャーアップグレードにはダンプ/リストアまたはpg_upgradeが必要で、各メジャー版では最新マイナーを使うよう勧めています。UUIDv7だけを理由に本番を急いで18へ上げるのではなく、通常のメジャーアップグレード計画へ組み込む判断になります。
すでにuuid型の主キーがあるなら、18へ移行した後に既定値だけを切り替えられます。
ALTER TABLE customer
ALTER COLUMN id SET DEFAULT uuidv7();
ALTER TABLEの仕様どおり、新しい既定値は以後のINSERTにだけ適用され、既存行は変わりません。また、PostgreSQLのuuid型は生成元やバージョンを問わずUUIDを保存できるため、UUIDv4とUUIDv7は同じ列で共存できます。
これは低リスクに試しやすい一方、既存のUUIDv4が並び直されたり、すでにある断片化が自動で解消されたりするわけではありません。切り替え前後で、バージョン別の件数、インデックスサイズ、pgstatindex()の密度、書き込みレイテンシ、バッファヒット率を同じ条件で追うと、採用効果を切り分けやすくなります。
SELECT uuid_extract_version(id) AS uuid_version, count(*)
FROM customer
GROUP BY uuid_extract_version(id)
ORDER BY uuid_version;
アプリケーション側でもIDを生成している場合は、DBの既定値だけでなく、各クライアントが発行するUUIDのバージョンを棚卸しする必要があります。DBとアプリで生成方式が分かれる構成では、どこがIDの責任を持つかも決めておきたいですね。
BIGINT・UUIDv4・UUIDv7はIDを作る場所と見せる情報で選ぶ
UUIDv7は、UUIDが必要な場面における有力な既定候補ですが、すべての主キーの正解ではありません。まず「複数ノードやクライアントが調整なしにIDを作る必要があるか」を切り分けると選びやすくなります。
| 候補 | 合いやすい条件 | 受け入れる点 |
|---|---|---|
BIGINT GENERATED ... AS IDENTITY |
1つのDBが採番を担い、IDを主に内部で使う | 分散した生成元には別の調整が必要 |
| UUIDv4 | 分散生成が必要で、作成時刻や順序をIDから見せたくない | ランダム挿入によるB-treeの局所性低下 |
| UUIDv7 | 分散生成が必要で、時刻順の局所性を活かしたい | おおよその生成時刻と順序が読み取れる |
UUIDが不要なら、8バイトのBIGINTは16バイトのUUIDよりコンパクトで、順次採番ならB-treeにも素直です。UUIDが必要でも、外部公開IDから時刻を推測されたくないならUUIDv4を残す理由があります。UUIDv7が効くのは、分散生成とB-treeの挿入局所性を両立したく、時刻情報が見えることを受け入れられる場面です。
したがって、冒頭の問いへの答えは「UUIDv7なら主キー問題がすべて解決する」ではありません。UUIDを本当に必要とするテーブルで、書き込みの局所性を改善したいなら、PostgreSQL 18のuuidv7()はまず比較したい選択肢です。既存テーブルでは既定値だけを段階的に替え、実際のインデックス密度とレイテンシを測るところから始めると、利点と代償を同じ土俵で判断できます。
出典
- Title: Shaun Thomas: The Time Traveler's Primary Key
- URL: https://postgr.es/p/9sX
Share
Related Articles
カテゴリやタグが近い記事を続けて読めるように並べています。




