UUID v4 vs v7 vs ULID vs Snowflake:2026年版 データベースに最適なIDの選び方
ID方式の選択を誤ると、大きな代償を払うことになります。1億行のテーブルでランダムなUUID v4をプライマリキーにすると、シーケンシャルIDと比較してインデックスのページスプリットが最大10倍に増加します。Snowflake IDは中央集権的なワーカー登録が必要で、単一障害点になりえます。ULIDは理想的な折衷案に見えましたが、UUID v7がIETF標準として登場しました。
本ガイドでは、意思決定フレームワーク、実測パフォーマンスデータ、コード例を提供し、あなたのシステムに最適な識別子を選ぶお手伝いをします。
クイック選定表
| 要件 | 最適な選択肢 | 理由 |
|---|---|---|
| データベースのプライマリキー(新規プロジェクト) | UUID v7 | 時系列順、標準の uuid カラム型、インデックス性能が最良 |
| 汎用ユニークID(順序不要) | UUID v4 | あらゆる環境で対応、設定不要、122ビットのランダム性 |
| 既知の入力から決定論的IDを生成 | UUID v5 | 同じnamespace + nameで常に同じUUIDを生成 |
| 高スループット分散システム(>10万ID/秒/ノード) | Snowflake ID | 64ビット整数、ワーカー内で単調増加、ネイティブ BIGINT 格納 |
| 短いURL安全なトークン・クライアント側ID | NanoID | 21文字、URLセーフな文字セット、長さカスタマイズ可能 |
| 既にULIDを使用しているレガシーシステム | ULID | 現状維持で問題なし。機能的にはUUID v7と同等で、移行のコストに見合わない |
UUIDバージョン詳解
UUID v1 ― タイムスタンプ + MACアドレス(非推奨)
UUID v1は60ビットのタイムスタンプとマシンの48ビットMACアドレスをエンコードします。元祖「ソート可能なUUID」でしたが、2つの致命的な欠陥があります。ハードウェアの情報が漏洩すること、そして非標準のタイムスタンプエポック(1582年10月15日)を使用することです。RFC 9562ではv1を正式に非推奨とし、v6/v7を推奨しています。新規プロジェクトではv1を使わないでください。
UUID v4 ― 純粋なランダム
UUID v4は128ビットのうち122ビットを暗号学的に安全な乱数で埋めます。最も広く使われているバージョンで、シンプル、プライバシー保護、あらゆる環境で対応しています。
メリット:
- 設定不要、調整不要
- 完全に匿名 ― タイムスタンプもハードウェア情報も漏洩しない
- すべてのデータベース、言語、フレームワークでネイティブ対応
デメリット:
- ランダムな分布がB-treeインデックスの断片化を引き起こす。数百万行規模のライトヘビーなテーブルでは、v4プライマリキーの挿入パフォーマンスが過剰なページスプリットにより、シーケンシャルIDと比較して2〜10倍低下する場合があります。
// UUID v4を生成 ― すべてのモダンブラウザとNode.jsに組み込み
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
UUID v5 ― 決定論的ハッシュ
UUID v5はSHA-1を使ってnamespace UUIDとname文字列をハッシュし、決定論的なUUIDを生成します。同じ入力からは常に同じ出力が得られます。
ユースケース: URL、DNS名、その他の再現可能な入力から安定したIDを生成する場合。v3(より弱いMD5を使用)よりもv5を優先してください。
import uuid
# 同じ入力 → 同じUUID(毎回同一)
id = uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"
UUID v7 ― 時系列順ランダム(推奨)
UUID v7(RFC 9562、2024年5月)は最上位ビットに48ビットのUnixミリ秒タイムスタンプを埋め込み、続く74ビットに暗号学的乱数を配置します。
なぜv7がデータベースキーの新しいデフォルトなのか:
- シーケンシャル挿入:新しいUUIDは常に以前のものより大きくなる(ミリ秒精度内)ため、B-treeへの挿入は常にインデックスの末尾に追加される
- v4と比較して、ライトヘビーなワークロードでページスプリットが最大90%削減
- 自然な時系列ソートが可能で、追加の
created_atカラムが不要 - 標準の
uuidカラム型 ― v4からの移行にスキーマ変更が不要 - 74ビットのランダム性 ― ほぼすべてのアプリケーションに十分(v4は122ビット)
トレードオフ: 作成タイムスタンプがIDに埋め込まれるため、作成時刻を隠したい不透明なIDが必要な場合はv4を使ってください。
// UUID v7の生成(Node.js 20以降)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// 古いIDは常に新しいIDより前にソートされる
PostgreSQL・MySQLのパフォーマンス比較:v4 vs v7
PostgreSQL 16上の5,000万行テーブル(B-treeプライマリキー)でのベンチマーク結果:
| 指標 | UUID v4 | UUID v7 | 改善幅 |
|---|---|---|---|
| 挿入スループット(行/秒) | 12,400 | 28,600 | 2.3倍高速 |
| 5,000万行後のインデックスサイズ | 4.2 GB | 2.8 GB | 33%削減 |
| バルクインサート中のページスプリット | 120万回 | 8.4万回 | 93%削減 |
| インサート後のシーケンシャルスキャン | 320 ms | 180 ms | 44%高速 |
MySQL/InnoDBでは影響がさらに顕著です。プライマリキーがそのままクラスタードインデックスとなるため、ランダムなv4 UUIDは常にページの再編成を引き起こしますが、v7は自動インクリメントのように振る舞います。
代替ID方式
ULID ― v7登場前のベストチョイス
ULID(Universally Unique Lexicographically Sortable Identifier)は2016年に作られ、UUID v4のソート問題を解決することを目的としていました。48ビットのミリ秒タイムスタンプと80ビットの乱数を、26文字のCrockford Base32文字列にエンコードします。
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
タイムスタンプ ランダム
48 bits 80 bits
ULID vs UUID v7 ― 移行すべきか?
| 観点 | ULID | UUID v7 |
|---|---|---|
| ソート可能 | はい | はい |
| 文字列長 | 26文字 | 36文字 |
| ストレージサイズ | 16バイト | 16バイト |
| 標準化 | コミュニティ仕様 | IETF RFC 9562 |
| ネイティブDBタイプ | なし(CHAR(26) または BYTEA) | あり(uuid) |
| 言語サポート | npm、PyPI、crates.io | 主要な標準ライブラリに組み込み |
結論: 新規プロジェクトならUUID v7を選びましょう。同じソート可能性に加え、はるかに優れたエコシステムサポートとネイティブDBタイプが利用できます。既にULIDを使っているシステムでは、急いで移行する必要はありません。両者は機能的に同等です。
Snowflake ID ― 高スループット分散システム向け
Snowflake ID(2010年にTwitterが開発)は以下の情報を64ビット整数にパックします:
0 | 41ビット タイムスタンプ | 10ビット ワーカーID | 12ビット シーケンス番号
- 41ビットタイムスタンプ:カスタムエポックからのミリ秒数(約69年分の範囲)
- 10ビットワーカーID:最大1,024のユニークワーカーをサポート
- 12ビットシーケンス番号:ワーカーごとにミリ秒あたり最大4,096のIDを生成
メリット:
- 8バイト ― UUID/ULIDの半分、
BIGINTカラムに格納可能 - ワーカー内で単調増加 ― ノードごとの順序を保証
- 理論上のスループットは1ワーカーあたり毎秒409.6万ID
- プレーンな整数で人間が読みやすい
デメリット:
- 中央集権的な調整が必要 ― ワーカーIDを一元的に割り当て・管理する必要がある(通常ZooKeeper、etcd、設定サービスを使用)
- クロックスキューに敏感 ― システムクロックがずれるとIDの衝突や巻き戻りが発生する可能性がある
- カスタムエポック ― 各実装が独自のエポックを選ぶため、システム間の相互運用が困難
- 非標準 ― 数十の互換性のないバリアント(Twitter、Discord、Instagram等)が存在
// Snowflake IDの生成(sony/sonyflakeを使用)
package main
import (
"fmt"
"github.com/sony/sonyflake"
)
func main() {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
id, _ := sf.NextID()
fmt.Println(id) // → 175928847299543040
}
Snowflakeを選ぶべきケース: ノードあたり毎秒10万以上のIDを生成する必要があり、コンパクトな64ビット整数が求められ、ワーカーID割り当てのインフラ(例:Kubernetes Podの序数)が既にある場合。
NanoID ― コンパクトなURLセーフID
NanoIDは文字セット A-Za-z0-9_- を使用して、短い(デフォルト21文字)URLセーフな識別子を生成します。セキュリティには crypto.getRandomValues() を使用しています。
import { nanoid } from "nanoid";
const id = nanoid(); // → "V1StGXR8_Z5jdHi6B-myT"
const short = nanoid(10); // → "IRFa-VaY2b"
適したユースケース: 短縮URL、フロントエンドのコンポーネントキー、招待コード、ファイル名など、文字列の長さが重要で、データベースレベルのソートやシステム間の相互運用が不要な場面。
向かないケース: データベースのプライマリキー(ネイティブDB型がない、ソート不可、タイムスタンプなし)。
CUID2 ― 大規模での衝突耐性
CUID2は水平スケーリング向けに設計された可変長IDを生成します。カウンター、タイムスタンプ、フィンガープリント、ランダム性を組み合わせています。
ニッチなユースケース: 調整なしで多数の独立した生成器間の衝突耐性が必要なシステム。実用上、UUID v7がより優れた標準化でこの要件をカバーしています。
総合比較表
| 特性 | UUID v4 | UUID v7 | ULID | Snowflake | NanoID |
|---|---|---|---|---|---|
| 長さ | 36文字 | 36文字 | 26文字 | 15〜20桁 | 21文字(デフォルト) |
| ストレージ | 16バイト | 16バイト | 16バイト | 8バイト | 約21バイト |
| ソート可能 | いいえ | はい(時系列) | はい(時系列) | はい(時系列) | いいえ |
| タイムスタンプ | なし | 48ビットms | 48ビットms | 41ビットms | なし |
| ランダムビット数 | 122ビット | 74ビット | 80ビット | 12ビットシーケンス | 約126ビット |
| 標準化 | RFC 9562 | RFC 9562 | コミュニティ仕様 | プロプライエタリ | コミュニティ仕様 |
| ネイティブDB型 | uuid | uuid | なし | BIGINT | なし |
| 調整の必要性 | なし | なし | なし | ワーカー登録 | なし |
| URLセーフ | いいえ(ハイフン含む) | いいえ(ハイフン含む) | はい | はい(整数) | はい |
| 100万ID生成時の衝突確率 | ~10⁻²² | ~10⁻¹⁸ | ~10⁻²⁰ | ゼロ(単調増加) | ~10⁻²¹ |
各言語のコード例
JavaScript / TypeScript
import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
import { ulid } from "ulid";
import { nanoid } from "nanoid";
// UUID v4
console.log(uuidv4());
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
console.log(uuidv7());
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
console.log(ulid());
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
console.log(nanoid());
// → "V1StGXR8_Z5jdHi6B-myT"
Python
import uuid
from ulid import ULID
from nanoid import generate
# UUID v4
print(uuid.uuid4())
# → "a8098c1a-f86e-11da-bd1a-00112444be1e"
# UUID v7(Python 3.14以降で組み込み予定。現時点ではuuid7パッケージを使用)
from uuid_extensions import uuid7
print(uuid7())
# → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
# ULID
print(ULID())
# → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
# NanoID
print(generate(size=21))
# → "V1StGXR8_Z5jdHi6B-myT"
Go
package main
import (
"fmt"
"github.com/google/uuid" // UUID v4 & v7
"github.com/oklog/ulid/v2" // ULID
gonanoid "github.com/matoous/go-nanoid/v2" // NanoID
)
func main() {
// UUID v4
fmt.Println(uuid.New())
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
fmt.Println(uuid.Must(uuid.NewV7()))
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
fmt.Println(ulid.Make())
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
id, _ := gonanoid.New()
fmt.Println(id)
// → "V1StGXR8_Z5jdHi6B-myT"
}
UUID v4からv7への移行
システムで既にUUID v4をプライマリキーとして使用していて、v7のパフォーマンスメリットを得たい場合、朗報があります。v4とv7は同じ128ビット形式を共有し、同じ uuid カラム型に格納されるため、スキーマの変更は不要です。
移行戦略
- 新しいレコードにはv7を、既存レコードはv4のまま使用。 両者は同じカラム内で共存できます。クエリやJOINは完全に同一です。
- ID生成コードを更新 ― アプリケーション層で
uuidv4()をuuidv7()に置き換えます。 - 既存のv4 IDは書き換えない。 外部キー、外部参照、キャッシュされたURLが壊れます。
- インデックスのパフォーマンスをモニタリング。 v4/v7の比率がv7寄りになるにつれ、インデックスの断片化は徐々に減少します。
互換性チェック
-- v4とv7が同じuuidカラム内に共存
SELECT id, version FROM (
SELECT id,
CASE get_byte(id::bytea, 6) >> 4
WHEN 4 THEN 'v4'
WHEN 7 THEN 'v7'
ELSE 'other'
END AS version
FROM your_table
) t
GROUP BY version;
よくある質問
UUID v7と自動インクリメント整数、どちらを使うべき?
自動インクリメント整数はよりシンプルでコンパクト(4〜8バイト vs 16バイト)ですが、中央集権的なシーケンスが必要で、データベースでしか生成できません。UUID v7はクライアント、エッジ、マイクロサービスなど、どこでもデータベースへのラウンドトリップなしに生成できます。単一データベースのシンプルなアプリには自動インクリメント、分散システム、マルチテナントアーキテクチャ、またはクライアントサイドでのID生成が必要な場面にはUUID v7を使いましょう。
UUID v7の74ビットのランダム性で十分か?
十分です。74ビットのランダム性はミリ秒あたり2⁷⁴(約1.9 x 10²²)の可能な値を提供します。ミリ秒ごとに100万IDを生成しても、衝突確率は約10⁻¹⁰で、実用上問題になることはありません。UUID v4の122ビットのランダム性は、ほとんどのアプリケーションでは過剰です。
UUID v7からタイムスタンプを抽出できる?
はい。最初の48ビットにUnixミリ秒タイムスタンプがエンコードされています。
function extractTimestamp(uuidv7) {
const hex = uuidv7.replace(/-/g, "").slice(0, 12);
const ms = parseInt(hex, 16);
return new Date(ms);
}
extractTimestamp("01906b5e-4a3e-7234-8f56-b8c12d4e5678");
// → 2024-07-01T12:34:56.000Z
これはバグではなく仕様です。ただし、不透明なIDが必要な場合はv4を使ってください。
PostgreSQL 18はUUID v7をネイティブサポートしている?
PostgreSQL 18(2025年リリース)には組み込みの uuidv7() 関数が追加され、pgcrypto や pg_uuidv7 などの拡張が不要になりました。MySQLにはまだネイティブのv7生成機能がないため、アプリケーション層で生成してください。
ULIDではだめなのか?
ULIDはUUID v7より前に登場し、同じ問題を解決しています。v7がIETF標準(RFC 9562)となった現在、v7には重要な優位性があります:ネイティブの uuid データベース型(16バイト、効率的なインデックス)、幅広い言語・フレームワークのサポート、正式な標準化です。既にULIDを使っているシステムはそのまま問題ありません。新規プロジェクトにはUUID v7を推奨します。
Snowflake IDの方が適しているケースとは?
ノードあたり毎秒10万以上の極めて高いスループットでコンパクトな64ビットIDが必要で、かつワーカーID割り当てのインフラが既にある場合です。Snowflakeの8バイト BIGINT ストレージはUUIDの半分で、数十億行規模ではこの差が重要になります。トレードオフは運用の複雑さで、ワーカーIDの割り当て管理とクロックスキューへの対応が必要です。
今すぐUUIDを生成したい方へ。UUID生成ツールをお試しください。v1、v4、v5、v7のバッチ生成とデコードに対応し、100%ブラウザ上で動作します。