ULIDをブラウザで生成する:UUIDとの違いとCrockford Base32の実装
主キーやログのIDに UUID v4 を使っていると、「生成した順に並ばない」ことが地味に効いてくる。データベースのインデックスは挿入位置がばらけて断片化しやすく、一覧を時系列で出したいだけなのに別途タイムスタンプ列が要る。ULID(Universally Unique Lexicographically Sortable Identifier)は、この不満を埋める128ビットの識別子だ。
要点だけ先に言うと、ULIDは先頭にミリ秒タイムスタンプを持つので、文字列として辞書順に並べると生成時刻順になる。表記は26文字・ハイフンなしで、36文字のUUIDより短くURLにも使いやすい。
UUID v4 と ULID の使い分け
| UUID v4 | ULID | |
|---|---|---|
| 構造 | 122ビットの乱数 | 48ビット時刻 + 80ビット乱数 |
| 表記 | 36文字(ハイフン区切り) | 26文字(ハイフンなし) |
| ソート | 順序に意味なし | 辞書順=生成時刻順 |
| 向く用途 | 時刻を漏らしたくない | 時系列の主キー・ログ |
ランダム性だけが欲しいなら UUID v4、時系列に並ぶ主キーが欲しいなら ULID。生成時刻を推測されたくない場面では、逆に ULID を避けて v4 を選ぶ。
構造:48ビット時刻 + 80ビット乱数
ULIDの128ビットは、先頭48ビットが「1970年からのミリ秒」、残り80ビットが乱数だ。これを Crockford Base32 で符号化して26文字にする。Crockford Base32 は 0-9 と、紛らわしい I, L, O, U を除いた A-Z の32文字を使う。
1 | |
タイムスタンプ48ビットは10文字、乱数80ビットは16文字に符号化され、合わせて26文字になる。
実装(依存ゼロ)
乱数は暗号学的に安全な crypto.getRandomValues を使う。ブラウザでもNode 19+でもグローバルに crypto が使える。
1 | |
encodeTime は数値を32進数に変換しているだけだ。JavaScriptの数値は倍精度浮動小数点だが、48ビット(約281兆)は安全整数(2^53)の範囲内なので Date.now() のミリ秒をそのまま扱える。
「同じミリ秒」での順序について
上の実装は、同じミリ秒内で複数生成すると乱数部が独立に振られるため、その中での生成順までは保証しない。厳密な単調増加(同一ミリ秒でも生成順にソート)が必要なら、公式仕様にある「同じミリ秒なら前回の乱数部に+1する」monotonic factory を実装する。多くの用途ではミリ秒精度の順序で十分なので、まずはシンプルな実装で始めて問題ない。
動かして試す
件数を指定してまとめて生成し、コピーできるツールを公開している。値の見た目や、生成順に並ぶことを確認したいときにどうぞ。
- ULID生成ツール(暗号学的乱数・最大1000件・すべてブラウザ内で完結): hashitosystem.com/tools/ulid/
- UUID(v4/v7)との比較はこちら: hashitosystem.com/tools/uuidgen/
なお、時系列ソート可能なIDが欲しいだけなら、UUID仕様に取り込まれた UUID v7 も選択肢になる。標準のUUID形式を保ちたいならv7、より短い表記が欲しいならULID、と好みで選べばよい。