なぜ今さら「UUID」なのか?PostgreSQLで主キー設計を考える君へ
現場でバリバリとデータベースを触っていると、主キーの設計について一度は悩むよね。「とりあえず `BIGSERIAL` でいいか」と連番のIDに逃げていないかな?
もちろん、連番IDはシンプルで使いやすい。だけど、マイクロサービス化が進んだり、分散システムを扱うようになったりすると、その「連番」という制約が逆に首を絞めることになるんだ。
今日は、そんな時に頼りになる相棒、PostgreSQLの「UUID型」について、実務的な視点で深掘りしてみよう。
—
UUIDって、結局何者?
UUID(Universally Unique Identifier)は、128ビットの識別子だ。要は「世界中で絶対に重複しないID」を、中央集権的な管理なしで生成できる仕組みのこと。
`550e8400-e29b-41d4-a716-446655440000`
見たことあるよね? この文字列が、DBの主キーとしてなぜ優秀なのか。それは「生成の際にDBに問い合わせる必要がない」からだ。
連番だと「今のIDがいくつなのか」を確認するためにDBへアクセスしてロックを取る必要があるけれど、UUIDならアプリ側で「せーの」で生成して `INSERT` すればいい。分散環境や、オフラインでデータを作って後から同期するような設計には、これ以上ない選択肢なんだ。
—
PostgreSQLでUUIDを使いこなす作法
PostgreSQLでUUIDを使うには、標準の `uuid-ossp` 拡張モジュールを有効にするのが定石だ。まずはこれを入れておこう。
— これを実行するだけで、UUID生成関数が使えるようになる
CREATE EXTENSION IF NOT EXISTS “uuid-ossp”;
実践的なテーブル定義
例えば、ユーザーテーブルを作るならこんな感じだ。
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
name text NOT NULL,
created_at timestamptz DEFAULT now()
);
ここで使っている `uuid_generate_v4()` は、ランダムな値を生成するタイプだ。非常にシンプルで、ほとんどのユースケースではこれで十分。
—
実務で注意すべき「落とし穴」
「じゃあ、全部UUIDにすればいいじゃん!」と思うかもしれないけれど、ここで先輩エンジニアからの忠告を一つ。
1. インデックスの断片化問題
UUID(特にランダムなv4)を主キーにすると、B-treeインデックスがバラバラの場所に挿入されることになる。データ量が数百万、数千万件と増えてくると、書き込み時にインデックスの書き換えコストがバカにならなくなるんだ。
もし、パフォーマンスがシビアな大規模テーブルなら、UUID v7(時刻情報を含んだUUID)の利用を検討してほしい。PostgreSQL 17以降なら標準サポートされているし、それ以前でもプラグインで対応できる。時刻順に並ぶから、インデックスの効率が劇的に改善するよ。
2. 人間が読めない問題
デバッグ中にログを見て「これ、どのユーザーのIDだっけ?」ってなるのは日常茶飯事だ。こればかりはUUIDの宿命。開発環境ではある程度割り切る必要があるけれど、外部公開するIDには、UUIDとは別に公開用の短いID(ナノIDなど)を併用する設計も検討する価値があるね。
—
まとめ:いつUUIDを採用すべきか
僕が後輩にアドバイスするなら、基準はこうだ。
- 連番ID(BIGSERIAL)でいいケース: 単一のデータベースで完結する小規模〜中規模アプリ。IDの順序がビジネス的に重要な意味を持つ場合。
- UUIDを採用すべきケース:
- マイクロサービスなど、DBが物理的に分かれている環境。
- 外部からIDを推測させたくない(連番だとURLをいじれば他のデータが見えてしまうセキュリティリスクがある)。
- マスタデータを各クライアント側で先行生成して同期したい場合。
UUIDは、一度導入すると後戻りが大変な「基盤」に近い部分だ。だからこそ、今の要件だけでなく、将来のスケールを想像しながら選んでみてほしい。
「なんとなく」ではなく、「この理由でUUIDを選んだ」と言えるようになれば、君も一人前のDBエンジニアだ。また何かあればいつでも聞いてくれ!
コメント