UUIDという「諸刃の剣」:PostgreSQLにおけるID設計の深淵
データベース設計において、主キー(Primary Key)の選定はまさに「最初の意志決定」であり、その後のスケーラビリティを決定づける重要なポイントだ。長年大規模なPostgreSQL環境を運用していると、新人エンジニアから「とりあえずUUIDでいいですよね?」と聞かれることがよくある。
そのたびに僕はこう答える。「UUIDは便利だが、その代償を知っているか?」と。
今日は、PostgreSQLにおけるUUID利用の是非と、その内部挙動、そして避けるべき罠について少し深く掘り下げてみたいと思う。
なぜ我々はUUIDに惹かれるのか
UUID(Universally Unique Identifier)の最大の魅力は、分散システムにおける「衝突の回避」にある。連番(Serial/Identity)のように中央集権的なシーケンス管理を必要としないため、アプリケーション側でIDを生成し、そのままDBへ流し込める。マイクロサービスアーキテクチャにおいては、この「自律性」が正義になることが多い。
PostgreSQLでは `pgcrypto` 拡張(古いバージョン)や、PostgreSQL 13以降標準となった `gen_random_uuid()` 関数を使えば、一瞬でIDが生成できる。
— PostgreSQL 13以降ならこれで十分
INSERT INTO users (id, name) VALUES (gen_random_uuid(), ‘Alice’);
しかし、この「便利さ」の裏側で何が起きているのか。ここからがエンジニアの腕の見せ所だ。
インデックスの断片化という「静かなる侵略」
UUID(特にv4)を主キーに採用した際、最も頭を悩ませるのがB-treeインデックスの断片化だ。
UUID v4はランダムである。つまり、インデックスに値を挿入する際、その値はB-treeのリーフノードのどこに配置されるか予測不能だ。常に新しいページが割り当てられ、ノードの分割(Page Split)が頻発する。
これが何を意味するか。
1. バッファキャッシュの効率低下: 関連するデータがメモリ上に集約されず、ディスクI/Oが激増する。
2. ストレージの肥大化: ページ分割の結果、各ページに空き容量が生じ、物理的なストレージ利用効率が低下する。
3. インデックスの肥大化: 書き込み負荷が高い環境では、これが数ヶ月単位でクエリパフォーマンスをジワジワと蝕んでいく。
パフォーマンストラブルの回避策:UUID v7の検討
もし君が今、新規プロジェクトでUUIDを採用しようとしているなら、v4ではなくUUID v7を強く推奨する。
UUID v7は「タイムスタンプ」を含んでいる。つまり、辞書順(ソート可能)に近い性質を持っているため、B-treeインデックスに追加する際、値が右端(末尾)に追記される傾向が強くなる。これにより、Page Splitは劇的に減り、挿入負荷はシーケンシャルな連番に近づく。
PostgreSQLの標準関数でv7がまだサポートされていない場合は、アプリケーション側で生成するか、または `ulid` のようなソート可能なIDスキームを採用するのも一つの手だ。
それでもUUIDを使うなら:物理設計での工夫
既存のシステムで既にランダムなUUIDを使っている場合、あるいはどうしてもv4を使わざるを得ない場合、どう立ち回るべきか。
- Fillfactorの調整: インデックスの作成時に `WITH (fillfactor = 90)` のように少し余裕を持たせることで、ページ分割の頻度を抑えることができる。これは書き込み負荷とのトレードオフだ。
- 物理ストレージの分離: 高頻度でアクセスされるインデックスのみ、高速なNVMeストレージへテーブルスペースを分けて配置するのも現実的な解だ。
- パーティショニング: UUIDの下位ビットでハッシュパーティショニングを行うことも検討に値する。これにより、一つのインデックスサイズを抑制し、管理のオーバーヘッドを分散できる。
結論:技術は「トレードオフ」を理解してこそ
UUIDは「分散環境でのIDの一意性」という大きなメリットをくれる。しかし、それは「B-treeの物理特性との戦い」という代償を伴う。
「とりあえずUUID」ではなく、「このワークロードなら、どんなID型が将来のパフォーマンスを担保できるか」を考えること。それが、データベースエンジニアとして一つ上のレイヤーにいくための条件だと僕は思う。
君の設計しているそのテーブル、1年後のインデックスの断片化率はどれくらいになりそうかな? 一度 `pgstattuple` で覗いてみることをお勧めするよ。
—
追伸:もし特定のワークロードでUUIDのパフォーマンスに苦しんでいるなら、まずは `EXPLAIN (ANALYZE, BUFFERS)` を取ってみてほしい。キャッシュヒット率の低さが、君の設計の答えを教えてくれるはずだ。
コメント