UUIDはほとんどのアプリケーションのデフォルトの一意識別子です。しかしUUID v4は——ランダム設計上——順序付けされていません。データベースに挿入された行は挿入順に保存されません。インデックスが断片化します。クエリが遅くなります。ULIDはまさにこの問題を解決するために設計されました。

ULIDを生成する →

ULIDとは何か

ULIDはUniversally Unique Lexicographically Sortable Identifier(汎用一意辞書順ソート可能識別子)の略です。2016年にAlizain Feerastaによって作成され、公開仕様で定義されています。

ULIDはこのようなものです:

01HQ7V5J3Z2QK8MNRPXTYW9E4B

26文字、大文字、Crockfordのbase32アルファベットを使用(視覚的な曖昧さを避けるためI、L、O、Uを除外)。

構造

 01HQ7V5J3Z  2QK8MNRPXTYW9E4B
|-----------|-----------------|
  タイムスタンプ    ランダム性
  48ビット         80ビット
  • タイムスタンプ(48ビット):Unixエポックからのミリ秒。西暦10895年まで有効。
  • ランダム性(80ビット):暗号論的にランダム。1ミリ秒あたり約1.2×10²⁴の可能な値。

ULID vs UUID:主な違い

特徴UUID v4ULID
長さ36文字(ハイフンあり)26文字
ソート可能いいえはい(辞書順)
タイムスタンプ埋め込みいいえはい
衝突確率極めて低い極めて低い
URLセーフいいえ(ハイフンを含む)はい
仕様RFC 4122ULID仕様

重要な違い:ULIDは作成時刻でソートされます。ULIDキーのテーブルに1000行挿入すると、プライマリキーインデックスがシーケンシャルに保たれます。これが重要な場面:

  • Bツリーインデックス:シーケンシャルな挿入は同じページにヒットし、断片化を減らす。
  • ページネーション:別のcreated_atカラムなしに効率的なカーソルとしてWHERE id > :cursorが使える。
  • ログファイル:ファイル名でソート = 時刻でソート。
  • メッセージキュー:別のシーケンス番号なしに自然な順序付け。

ULID vs UUID v7

UUID v7(RFC 9562、2024年確定)も最初の48ビットにミリ秒タイムスタンプを埋め込んでいます——ソート可能にするために。これはULIDの成功への直接的な対応です。

特徴ULIDUUID v7
フォーマット26文字Base3236文字16進数(ハイフンあり)
RFC標準いいえはい(RFC 9562)
DBネイティブ型TEXTまたはBINARYUUID型(PostgreSQL)

データベースにネイティブのUUIDサポートがある場合(PostgreSQLのuuid型)、UUID v7の方が便利かもしれません。文字列ベースのシステムで作業する場合や短いIDが必要な場合、ULIDは依然として良い選択です。

同一ミリ秒内のモノトニシティ

同一ミリ秒内に複数のULIDが生成された場合、タイムスタンプ部分は同じになります。ULID仕様はモノトニシティ拡張を定義しています:同一ミリ秒内の後続のULIDごとにランダムコンポーネントを1ずつインクリメントします。これにより高スループット(毎秒数百万の挿入)でも順序付けが保証されます。

コードでのULID生成

JavaScript / TypeScript

npm install ulid
import { ulid } from 'ulid';

const id = ulid();
// "01HQ7V5J3Z2QK8MNRPXTYW9E4B"

// カスタムタイムスタンプを使用
const id2 = ulid(Date.now());

Go

go get github.com/oklog/ulid/v2
import (
    "math/rand"
    "time"
    "github.com/oklog/ulid/v2"
)

ms := ulid.Timestamp(time.Now())
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
id := ulid.MustNew(ms, entropy)
fmt.Println(id) // "01HQ7V5J3Z2QK8MNRPXTYW9E4B"

Python

pip install python-ulid
from ulid import ULID

id = ULID()
print(str(id))  # "01HQ7V5J3Z2QK8MNRPXTYW9E4B"
print(id.timestamp())  # datetimeオブジェクト

データベースへのULIDの保存

PostgreSQL

-- テキストとして(26バイト)
CREATE TABLE events (
  id TEXT PRIMARY KEY,
  ...
);

PostgreSQLはULIDをネイティブに理解しませんが、TEXTまたはBYTEAとして保存できます。多くのチームはわかりやすさのためにTEXTを使用します。26バイトのコストはクエリのシンプルさと比べると無視できます。

MySQL / SQLite

同じアプローチ——CHAR(26)またはVARCHAR(26)として保存します。固定幅文字列なのでCHAR(26)の方がわずかに効率的です。

ULIDのデコード

タイムスタンプは最初の10文字に埋め込まれています。抽出できます:

import { decodeTime } from 'ulid';

const ts = decodeTime('01HQ7V5J3Z2QK8MNRPXTYW9E4B');
console.log(new Date(ts)); // 作成日時

これはデバッグに有用です:created_atを照会せずにIDだけからレコードがいつ作成されたかを正確に確認できます。

ULIDをいつ使うか

  • 高挿入レートのテーブルでインデックス断片化が懸念される場合
  • イベントログ、監査証跡、または追記が多いワークロード
  • 別のタイムスタンプカラムなしに時刻順のIDが必要なシステム
  • UUIDより短いURL安全で人間が読めるIDが必要なAPI

ULIDを今すぐ生成する →

関連ツール