UUID v7ジェネレーター
Generate time-ordered UUID v7 for database primary keys
…
フォーマット
UUID v7 とは何ですか?
UUID v7 は RFC 9562(2024 年 5 月)で標準化された次世代 UUID フォーマットです。最上位ビットに 48 ビットの Unix ミリ秒タイムスタンプをエンコードし、続いてバージョンとバリアントマーカーが来て、残りのビットは暗号学的に安全なランダムデータで埋めます。
タイムスタンプが上位ビットを占めるため、UUID v7 の値は時系列で並べ替えられます——辞書順でも数値順でも。これにより、ランダムな UUID(v4)が B ツリーインデックスの断片化を引き起こすデータベースの主キーとして非常に適しています。
ランダム UUID がデータベースインデックスを断片化させる理由
B ツリーインデックス——PostgreSQL、MySQL、SQLite およびほとんどの他のデータベースで使用——はキー値でソートして行を保持します。新しい行を挿入すると、データベースはインデックス内の正しいソート位置に配置する必要があります。
UUID v4(完全にランダム)では、各新規挿入はインデックスツリーの本質的にランダムな位置に落ちます。これにより、データベースは内部インデックスページを常に読み取りおよび書き直しを強いられ、満杯のページを分割し、他のページを半空にします。結果として断片化した肥大化したインデックスが生まれ、テーブルが成長するにつれて書き込みと読み取りの両方が遅くなります。
UUID v7 のビットレイアウト
UUID v7 は 128 ビット幅で、6 つのフィールドに分割されます:
| ビット | フィールド | 目的 |
|---|---|---|
| 48 | unix_ts_ms | 48 ビット Unix タイムスタンプ(ミリ秒)——上位半分全体を占める |
| 4 | ver | バージョン番号——常に 0111(10 進数 7) |
| 12 | rand_a | 12 ビットの暗号学的に安全なランダムデータ |
| 2 | var | バリアントマーカー——常に 10(RFC 4122 バリアント) |
| 62 | rand_b | 62 ビットの暗号学的に安全なランダムデータ |
タイムスタンプ精度は 1 ミリ秒です。同一ミリ秒内では、UUID v7 の値はランダムサフィックスで順序付けられます——サブミリ秒での単調増加は保証されませんが、k-sortable(k ソート可能)です:時間的に近くに生成された ID はインデックスでも近くに並びます。
UUID v7 vs UUID v1
UUID v1 と UUID v7 はどちらもタイムスタンプを埋め込みますが、設計において大きく異なります:
| 機能 | UUID v7 | UUID v1 |
|---|---|---|
| エポック / 時間ベース | Unix エポック(1970 年 1 月 1 日) | グレゴリオ暦エポック(1582 年 10 月 15 日) |
| 時間精度 | 1 ミリ秒 | 100 ナノ秒 |
| ソート可能 | はい——設計上 k ソート可能 | いいえ——タイムスタンプフィールドが UUID レイアウトでスクランブルされている |
| プライバシー | ホスト情報の漏洩なし | 生成ホストの MAC アドレスを埋め込む |
| DB インデックスパフォーマンス | 優秀——順次挿入、最小断片化 | 劣る——タイムスタンプがあるにもかかわらず非順次 |
| 標準 | RFC 9562(2024 年) | RFC 4122(2005 年) |
| ネイティブブラウザサポート | 未対応(crypto.randomUUID v7 なし) | ネイティブでは利用不可 |
時間順 UUID が必要な新プロジェクトには、UUID v1 より UUID v7 を優先してください。UUID v1 はレガシーであり、ホスト情報を漏洩します。
UUID v7 vs ULID
ULID(Universally Unique Lexicographically Sortable Identifier)は UUID v7 と同様の問題を解決します。比較を示します:
- RFC 9562 UUID 標準に準拠——すべての UUID ツールと互換性あり
- ハイフン付き 16 進数フォーマット——普遍的に認識される
- ネイティブデータベース UUID カラムサポート
- 合計 128 ビット
- Crockford Base32 エンコーディング——26 文字、やや コンパクト
- 大文字小文字を区別せず、あいまいな文字(I、L、O、U)を避ける
- 一目で人間に読みやすい
- ライブラリが必要——ネイティブプラットフォームサポートなし
すでに UUID エコシステム(PostgreSQL uuid カラム、UUID を返す REST API)にいる場合は UUID v7 を使用してください。ゼロから始めてより人間にやさしいエンコーディングを好む場合は、ULID が合理的な代替手段です。
データベースでの UUID v7 の使用
UUID v7 はほとんどのデータベースでまだネイティブに生成されませんが、標準 UUID カラムに保存でき、アプリケーションコードまたは拡張機能を通じて生成できます:
uuid カラムに保存します。pg-uuidv7 拡張は、DB 生成の ID が必要な場合に uuid_generate_v7() サーバーサイド関数を追加します。BINARY(16) または CHAR(36) カラムに保存します。アプリケーションコードで生成します。MySQL 8.0+ は UUID_TO_BIN(UUID(), 1) で v1 の順序付き UUID をサポートしますが、v7 はアプリレベルでの生成が必要です。TEXT(36 文字)または BLOB(16 バイト)として保存します。アプリケーションコードで生成します。UUID v7 は固定幅のタイムスタンププレフィックスを使用するため、TEXT での辞書順ソートは正しく動作します。コード例
UUID v7 はまだ crypto.randomUUID() では利用できません。ネイティブサポートが来るまで uuidv7(npm)などのライブラリを使用してください:
function generateUuidV7() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
// Embed 48-bit Unix ms timestamp
buf[0] = Number((ms >> 40n) & 0xFFn)
buf[1] = Number((ms >> 32n) & 0xFFn)
buf[2] = Number((ms >> 24n) & 0xFFn)
buf[3] = Number((ms >> 16n) & 0xFFn)
buf[4] = Number((ms >> 8n) & 0xFFn)
buf[5] = Number(ms & 0xFFn)
// Set version 7 (0111xxxx)
buf[6] = (buf[6] & 0x0F) | 0x70
// Set variant (10xxxxxx)
buf[8] = (buf[8] & 0x3F) | 0x80
const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}
// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto' // v4 only — no v7 yet in stdlib# pip install uuid7 import uuid_extensions uid = uuid_extensions.uuid7() print(uid) # e.g. 018e2b3d-1a2b-7000-8000-abc123456789 print(uid.time) # Unix ms timestamp embedded in the UUID # Or as a plain string from uuid_extensions import uuid7str print(uuid7str())
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
SELECT encode(
set_bit(
set_bit(
overlay(
uuid_send(gen_random_uuid())
PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
FROM 1 FOR 6
),
52, 1
),
53, 1
),
'hex'
)::uuid;
$$;
-- Usage as a default primary key
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
payload jsonb,
created_at timestamptz DEFAULT now()
);function extractTimestamp(uuid: string): Date {
const hex = uuid.replace(/-/g, '')
const ms = parseInt(hex.slice(0, 12), 16) // first 48 bits = ms timestamp
return new Date(ms)
}
const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"