🆔
概念 #データ設計 #分散ID #UUID #ULID #Snowflake ID #DDIA 📚 データ志向アプリケーション設計(DDIA)

分散ID生成

UUID・ULID・Snowflake IDの比較。なぜUUID v4はインデックスに悪いか、ULIDとUUID v7がどう解決するか。モノトニックIDの生成戦略を理解する

定義

分散ID生成:複数のノードが中央コーディネーターなしに、グローバルに一意なIDを生成する手法。マイクロサービスやシャーディングされたDBでは、中央DBの自動採番(SERIAL / AUTO_INCREMENT)が使えないためこの問題が発生する。

要件の整理

IDに求められる性質:
  ✅ グローバルに一意(衝突しない)
  ✅ 生成が速い(高スループット)
  ✅ ソート可能(時系列順に並べられる)← これが重要
  ✅ DBインデックスに優しい
  ✅ 推測不可能(セキュリティ)

すべてを同時に満たすのが難しい

SERIAL / AUTO_INCREMENT の限界

-- PostgreSQLのSERIAL(内部的にはSEQUENCE)
CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  name VARCHAR(100)
);

問題

  • 単一DBへの依存 → シャーディング不可
  • 順番が推測可能(/users/1, /users/2… → ユーザー数が漏れる)
  • マージ時の衝突(2つのDBを統合すると重複が起きる)

UUID v4

ランダムな128bitの値。

形式: 550e8400-e29b-41d4-a716-446655440000
生成: 122bitの擬似乱数

✅ 衝突確率が無視できるほど低い(2^122通り)
✅ 推測不可能
✅ 分散生成可能(中央不要)
❌ 完全ランダム → ソート不可
❌ BツリーインデックスのPageSplitが頻発

UUID v4がインデックスに悪い理由

Bツリーはキーをソートされた状態で保持する

SERIAL(単調増加):
  新しい行は常に右端のページに追加
  → ほぼすべてのページがキャッシュに乗る
  → 書き込みI/O最小

UUID v4(ランダム):
  新しい行がランダムな位置に挿入される
  → ランダムなページをキャッシュに読み込んでは捨てる
  → ページ分割(Page Split)が頻発してインデックスが断片化
  → Write Amplificationが大きい
  
負荷テスト結果(目安):
  UUID v4 vs SERIAL → 書き込みスループットが50〜70%低下

ULID(Universally Unique Lexicographically Sortable Identifier)

形式: 01ARZ3NDEKTSV4RRFFQ69G5FAV
構造:
  |--------10文字---------|--------16文字----------|
  タイムスタンプ(48bit)       ランダム(80bit)

✅ ミリ秒精度のタイムスタンプでソート可能
✅ 同一ミリ秒内でもランダム部分で一意
✅ Crockford Base32で人間が読みやすい
✅ UUIDと同じ128bit(DB上のUUID型に格納可能)
❌ 同一ミリ秒に大量生成すると順序が保証されない
import { ulid } from 'ulid';

const id = ulid();
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"

// 時系列順にソート可能
const ids = [ulid(), ulid(), ulid()];
ids.sort(); // 生成順と一致

UUID v7(RFC 9562、2024年確定)

構造:
  |--48bit--|-4bit-|-12bit--|--2bit--|----62bit random----|
  unix_ms    ver    rand_a   variant      rand_b

✅ ULID同様にタイムスタンプ付きでソート可能
✅ UUID形式を維持(既存ツールと互換)
✅ RFC標準(2024年4月確定)
→ ULIDの後継としてデファクトになりつつある
-- PostgreSQL 17からネイティブサポート予定
-- 現在はuuid-ossp拡張またはアプリ側で生成
SELECT gen_random_uuid();    -- UUID v4(現在のデフォルト)
-- UUID v7はpg_uuidv7拡張で利用可能

Snowflake ID(Twitter発)

構造(64bit整数):
  |--41bit--|--10bit--|--12bit--|
  タイムスタンプ  マシンID  シーケンス

✅ 64bit整数 → BIGINTとして格納できる(128bitより小さい)
✅ タイムスタンプでソート可能
✅ 毎ミリ秒4096個まで生成可能
❌ マシンIDの管理が必要(分散環境で唯一性を保証するコーディネーター)
❌ タイムスタンプのエポック設定が必要

Discordは2015年1月1日をエポックにしたSnowflake IDを採用。

Discord ID の例: 175928847299117063
  タイムスタンプ: 175928847299117063 >> 22 = 41944705582 ms
                  → 2015-01-01からの経過ミリ秒

CUID2 / NanoID

セキュリティを優先する場面向け。

CUID2:
  衝突耐性の高いランダムID
  プレフィックスで識別(user_01H2G3K4M...)
  → 推測不可能、URL安全

NanoID:
  21文字のランダムID(UUID v4と同等の衝突耐性)
  カスタムアルファベット可能
  UUID v4より短い

採用指針

要件に応じた選択:

順序が重要 + 分散生成 + UUID互換:
  → UUID v7(標準化済み、推奨)
  → ULID(UUID v7登場前のデファクト)

順序が重要 + 整数型で格納したい:
  → Snowflake ID(マシンID管理の仕組みが必要)

セキュリティ優先 + 短さが重要:
  → CUID2 / NanoID

単一DBで十分 + 推測されても問題ない:
  → BIGSERIAL(最もシンプル)

DBのID型選択

-- PostgreSQL でのUUID格納
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
-- → 16バイト固定長、インデックスが効率的

-- UUID を文字列で格納(アンチパターン)
id VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::text
-- → 36バイト(ハイフン込み)、比較・インデックスが遅い

-- UUID v7 / ULID を格納する場合
-- UUIDのバイナリ互換なのでUUID型をそのまま使える

セキュリティ上の注意

IDに含まれる情報:
  SERIAL: ユーザー数・作成順序が推測可能
  Snowflake: タイムスタンプが含まれる(作成時刻がバレる)
  UUID v4 / CUID2: 推測困難

外部公開IDと内部IDの分離:
  内部: BIGSERIAL(高速、シンプル)
  外部API: UUID v7 or CUID2(推測不可)
  
  → 外部IDから内部IDへのマッピングをDBまたはアプリで管理

関連概念

出典・参考文献

  • Martin Kleppmann, Designing Data-Intensive Applications (2017) Chapter 9
  • Twitter Engineering, “Snowflake” (2010)
  • RFC 9562, “Universally Unique IDentifiers (UUIDs)” (2024)
  • ULID Specification — github.com/ulid/spec
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー

出典: Martin Kleppmann, 'Designing Data-Intensive Applications' (2017) Chapter 9