ULID 生成

ソート可能な 26 文字の ULID(Crockford base32)を生成し、埋め込まれたタイムスタンプを日付へデコード。データベース主キーに最適。

ソート可能な一意識別子。 ULID は 26 文字で、先頭 10 文字が作成時刻(ミリ秒)を、末尾 16 文字が乱数を表します。そのため辞書順でソートでき、主キーとして安全に使えます。ブラウザ内生成。

ULID と UUID の使い分け

ULID とは何か

ULID は UUID と同じ 128 ビット幅の識別子で、48 ビットのミリ秒タイムスタンプと 80 ビットの乱数という 2 つの部分から成ります。表記は UUID のハイフン付き 36 文字 16 進ではなく、Crockford base32 の 26 文字です。そのため短く、大文字小文字を区別せず、最も読み間違えやすい文字を含みません。アルファベットから I、L、O、U が除外されています。

本質的な違いはエンコードではなく順序性です。タイムスタンプが上位ビットを占め、base32 がバイト順を保つため、ULID を単なる文字列としてソートすると生成時刻順に並びます。同じミリ秒に生成された 2 つの ID は乱数部分で決まりますが、ミリ秒をまたげば順序は正確です。

ソート可能性がデータベースで効く理由

UUIDv4 は一様な乱数であり、クラスタ化インデックスにとってはまさに避けたい性質です。挿入のたびに B ツリーのランダムな位置に着地するため、データベースはページ分割を繰り返し、コールドなページに触れ続けます。インデックスは必要以上に肥大し、テーブルの成長とともに書き込みスループットが劣化します。MySQL の InnoDB や SQL Server における UUID 主キーの問題としてよく知られています。

時刻順の識別子はインデックスの右端付近に追加されるため、アクセスパターンは自動採番の整数と同じです。ID 取得のための往復が不要、サービス間の調整が不要、シャード間のマージが安全という、クライアント生成のグローバル一意キーの利点を保ったまま、インデックスの断片化を避けられます。

別の選択肢を採るべき場面

RFC 9562 で標準化された UUIDv7 は、標準 UUID の書式とレイアウトのまま ULID と同じことを実現します。すでにネイティブの UUID 列型やツールが揃っているスタックなら、既存のエコシステムにそのまま収まるという理由だけで、現在は UUIDv7 のほうが適切なことが多いでしょう。URL やログで短く扱いやすいテキスト形式が欲しい場合には ULID に依然として利点があります。

いずれにせよ、タイムスタンプは ID を持つ誰にでも見えることを理解しておいてください。生成時刻は漏れ、隣接する ID は前後関係を明かします。列挙や時刻が機密にあたる用途には使わないでください。1 ミリ秒あたり 80 ビットの乱数があるため衝突は実務上の懸念ではありませんが、ULID は秘密情報ではなく、アクセストークンとして使ってはいけません。

オープンソースに関する注記:crypto.getRandomValues と自前の Crockford base32 コーデックで実装。サードパーティライブラリは使用していません。

よくある質問

ULID と UUID はどう違いますか?
どちらも 128 ビットですが、ULID は先頭に 48 ビットのミリ秒タイムスタンプを置くため、文字列としてソートすると作成時刻順になります。UUIDv4 は完全にランダムで並び順に意味がありません。
なぜデータベースでソート可能性が重要なのですか?
ランダムな主キーは挿入を B ツリー全体に散らばらせ、インデックスを断片化させます。時刻順のキーは末尾付近に追記されるため、書き込みが逐次的になりホットページも小さく保たれます。
同じミリ秒に生成した ID も順序を保てますか?
単調モードなら保てます。ランダム部分を引き直すのではなくインクリメントするため、同一ミリ秒内に生成した複数の ID も順序どおりに並びます。
ULID を公開しても安全ですか?
ランダム部分は推測できませんが、タイムスタンプは誰でも読み取れます。レコードの作成時刻が機微な情報であれば、ULID を公開識別子に使わないでください。
ULID と UUIDv7 のどちらを選ぶべきですか?
解決する課題は同じです。UUIDv7 は RFC 9562 で標準化され既存の UUID カラムやライブラリにそのまま収まるため、26 文字の Crockford 形式が特に必要でなければ新規開発では UUIDv7 を推奨します。
なぜ 26 文字なのですか?
128 ビットを Crockford Base32 で符号化すると 26 文字になります。この文字集合は I・L・O・U を除外しており、転記ミスと偶発的な単語の生成をどちらも防ぎます。