はじめに
主キーの選択はシステム全体の設計に大きく影響します。ナチュラルキーのように意味を持った値をそのまま主キーに使う方法もありますが、スキーマやビジネスルールが変わったときに影響が出やすく、運用が進むほど扱いづらくなることがあります。サロゲートキーはその問題を避けるための方法として広く選ばれています。
サロゲートキーにも様々あり、それぞれ異なる特徴を持っています。
なんとなくで、過去につかったことのある識別子を選びがちなので、サロゲートキーでよく使われる識別子を幅広く取り上げ、用途に応じた選び方をまとめます。
ナチュラルキーとサロゲートキー
ナチュラルキーは社員番号や商品コードのように、既に意味を持つ値を主キーとして利用する方式です。
理解しやすい反面、複合キーになりやすく、値が変わると全体に影響が及びます。
サロゲートキーは主キー専用に新しく割り当てる値を使います。
意味を持たないため、業務上の変更から切り離され、テーブル構造やJOINを単純に保ちやすいのが特徴です。
今のWebアプリケーションではこちらを採用することが多くなっているように思います。
主なサロゲートID方式
以下は、実務でよく使われるID方式を、形式例とともに紹介したものです。
特にRDSなどリレーショナルDBで主キーとして採用する前提で、
順序性・インデックス効率・衝突耐性・URL適性などの観点から整理しています。
Auto-Increment(連番)
形式の例
1
2
3
単一ノードのDBでは扱いやすく、インデックス効率も高い方式です。
ただし分散環境では競合が発生しやすく、推測可能性が高いという問題があります。
DDD(ドメイン駆動設計)でエンティティ生成時にIDが必要なような設計にも向きません。
メリット
- シンプルで理解しやすい
- インデックス効率が良い
- ソートしやすい
デメリット
- 分散環境で重複回避が困難
- IDが予測されやすい
- DB依存のためアプリ側で生成できない
UUIDv4
RFC 9562(Version 4)
- https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-4
- 日本語訳:https://tex2e.github.io/rfc-translater/html/rfc9562.html#5-4–UUID-Version-4
形式の例
67a03639-6c95-4baf-ad2a-499f21b0c49a
ランダムな128bitの識別子で、分散環境でも扱いやすい方式です。
ただし順序性がなく、インデックスが分散して非効率になりがちという欠点があります。
RDBによってはUUID型をサポートしており、文字列よりも効率的に扱える場合があります。
メリット
- 標準規格で広く利用されている
- 分散生成に強い
- 乱数ベースのため予測されにくい
デメリット
- 時系列ソートができない
- インデックス効率が悪い(ランダム書き込み)
- 文字列が長く扱いづらい
UUIDv7
RFC 9562(Version 7)
- https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7
- 日本語訳:https://tex2e.github.io/rfc-translater/html/rfc9562.html#5-7–UUID-Version-7
形式の例
019afb75-c9c9-7531-839f-0e61dd6adf90
生成時刻を含むUUIDで、UUIDv4よりインデックス効率が高く、時系列で自然に並ぶのが特徴です。
既存のUUID型とも互換性があるため、UUID文化と親和性が高い方式です。
近年の推奨方式として注目されています。
RFCの仕様ではミリ秒が同一でもカウンタにより単調性が維持されるため、生成順のソートが安定します。
メリット
- 時系列ソート可能
- UUIDとして扱えるためサポートが広い
- インデックス効率が向上する
- RFCの仕様ではミリ秒が同一でもカウンタにより単調性が維持されるため、生成順のソートが安定する
デメリット
- 実装がまだ新しく、サポートが環境によりまちまち
- UUIDv4よりは推測しやすい(時間情報が含まれる)
ULID
形式の例
01KBXQD307JZCN196JNP14VED3
タイムスタンプ + ランダム値の組み合わせで、文字列のままソート可能な方式です。
UUIDより短く見やすく、ログの確認やIDをそのままUIに出す用途でも扱いやすい点が特徴です。
メリット
- ソート可能
- UUIDより短く読みやすい
- ランダム部分が多く衝突しにくい
デメリット
- 標準規格ではない(UUIDほど統一されていない)
- ミリ秒単位で衝突対策が必要(実装ごとに差が出る)
NanoID
形式の例
kCgd3_BXMsxNAuFtjR8cM
短くURL-safeな識別子で、外部APIや公開URLなどに向いています。
ランダムベースで順序性はありません。
メリット
- とても短い
- URL-safe
- 強いランダム性
デメリット
- 時系列ソート不可
- 主キーにする場合はインデックス効率が悪い
CUID2
形式の例
gdojwz4049is65fl4zp7svjy
NanoIDと同様にURLとの相性が良く、外部に公開しやすいフォーマットのIDです。
こちらもランダム性重視で順序性はありません。
メリット
- 衝突しにくいように工夫されたランダムアルゴリズム
- URL-safeで読みやすい
デメリット
- 時系列ソート不可
- 標準規格ではない
KSUID
形式の例
36XbBMCi3jM1qZRT56hKVsuOgR2
ULIDと同様にソート可能で、rnd部分が大きく衝突しにくい形式です。
ログやイベントのIDとしてよく利用されます。
メリット
- 時系列ソート可能
- 乱数部分が大きく衝突しにくい
- URL-safe
デメリット
- 文字列がやや長い
- 標準規格ではない
XID(Go系で人気)
形式の例
693624266bcc5e73c28acf55
MongoDBのObjectIDに近い構造で、URL-safe・コンパクト・高速に生成できるID方式です。
Goのマイクロサービスでよく使われます。
メリット
- 時系列ソート可能
- コンパクトで URL-safe
- 高速生成
デメリット
- 暗号学的には強くない(予測可能性がある)
- 標準規格ではない
ShortUUID
形式の例
mcd53yA9kke8NvynYxvGFS
UUIDを短く読みやすい文字列に変換したID形式で、
URLに載せやすく、外部APIやUI表示に向いています。
ShortUUIDはUUIDを高効率な文字列表現に変換したもので、
文字数は短くなるが内部のデータ量(16bytes)はUUIDと同じです。
そのためUUID型で保存する場合のバイナリサイズは16bytesのままです。
メリット
- UUIDを短く表現できる
- URL-safe
- ランダム性はUUIDと同等
デメリット
- 順序性なし
- 基本は「エンコードを変えたUUID」であり構造的な改善ではない
ID方式比較表
| ID 方式 | 時系列ソート | 分散生成 | インデックス効率 | 衝突耐性 | URL 適性 | 文字列長 | バイナリサイズ |
|---|---|---|---|---|---|---|---|
| Auto-Increment(BIGINT) | ◎ | ✕ | ◎ | △ | ✕ | 最大19桁 | 8 bytes(BIGINT) |
| UUIDv4 | ✕ | ◎ | ✕ | ◎ | △ | 36文字 | 16 bytes(UUID型) |
| UUIDv7 | ◎ | ◎ | ○ | ◎ | △ | 36文字 | 16 bytes(UUID型) |
| ULID | ◎ | ◎ | ○ | ◎ | ○ | 26文字 | 16 bytes (バイナリ格納時) |
| NanoID | ✕ | ◎ | ✕ | ◎ | ◎ | 21文字程度 | 可変(text/varchar) |
| CUID2 | ✕ | ◎ | ✕ | ◎ | ◎ | 24〜32文字 | 可変(text/varchar) |
| KSUID | ◎ | ◎ | ○ | ◎ | ○ | 27文字 | 20 bytes |
| XID | ◎ | ◎ | ○ | ○ | ◎ | 20文字 | 12 bytes |
| ShortUUID | ✕ | ◎ | ✕ | ◎ | ◎ | 約22文字 | 元は16 bytes(UUID)。格納時はtextなら22bytes |
NanoIDやCUID2はUUIDのような固定構造を持たず、
ランダム文字列として生成されるため、「バイナリサイズ=文字列長」と考えるのが自然です。
一方でKSUIDは内部で20bytesの構造を持ち、UUID(16bytes)よりも少し大きい固定長の識別子です。
私の場合
私のチームではDDDを採用することが多く、
永続化前にIDを生成できる方式との相性が良い傾向があります。
そのため、アプリケーション側で安全に発行できるUUIDv7、ULID、CUID2、NanoIDなどを選択することが多いです。
次のステップとして、以下のような観点でID方式を比較していきます。
- URLとして外部に露出するか
- インデックスパフォーマンスをどれほど重視するか
- 時系列順のソート(順序性)が必要か
- DBとの相性
- 開発フレームワークあるいはORMとの相性
また、内部IDと公開IDを分離することで、セキュリティと扱いやすさの両方を満たせるケースもあります。
例えば、下記のような構成が有力な選択肢になります。
- 内部主キー:UUIDv7またはULID
- 公開ID(URL用など):NanoID / CUID2 / ShortUUID
- ビジネスキー:別カラムでUNIQUE制約を保持(業務要件と整合性を取るため)
まとめ
IDの選択は「一意性を確保する」ことだけが目的ではありません。
運用性、性能、公開範囲、分散性、将来の拡張性といった多くの要素が絡みます。
システムの背景やドメインモデルに合わせて、最適な方式を選択していきたいですね。
参考