ULID生成ツール:構造・特性・利用ガイド
ULIDの内部構造:128ビットをどう分割するか
ULID(Universally Unique Lexicographically Sortable Identifier)は、128ビットのデータを26文字の文字列にエンコードした識別子です。この128ビットは明確に二つの部分に分割されます。
- 先頭10文字(48ビット):ミリ秒精度のタイムスタンプ。Unixエポック(1970年1月1日 00:00:00 UTC)からの経過ミリ秒数をCrockfordのbase32でエンコードしたものです。
- 残り16文字(80ビット):暗号論的乱数によって生成されたランダム成分。
例えば、01ARZ3NDEKTSV4RRFFQ69G5FAV というULIDの場合、01ARZ3NDEK がタイムスタンプ部分、TSV4RRFFQ69G5FAV がランダム部分です。この分割により、ULIDは生成時刻で辞書順にソート可能になります。ただし、同一ミリ秒内で生成された複数のULIDの順序はランダム成分に依存するため、厳密な生成順序は保証されません。
タイムスタンプ部分は約7.7×10^13ミリ秒(約2,400年)をカバーできるため、現実的な使用期間ではオーバーフローの心配は不要です。
Crockfordのbase32:なぜ大文字小文字を区別せず、ハイフンがないのか
ULIDはCrockfordのbase32という特定のアルファベットを使用します。このアルファベットは以下の文字で構成されています。
- 数字:0〜9
- 英字:A〜Z から I、L、O、U を除外したもの(つまり A B C D E F G H J K M N P Q R S T V W X Y Z)
これにより、視覚的に紛らわしい文字が排除されます。例えば、1(数字の1)と I(大文字のアイ)、0(ゼロ)と O(オー)などが混同されにくくなります。
さらに、Crockfordのbase32は大文字と小文字を区別しません。つまり、a と A、b と B はすべて同じ値として扱われます。これは手動入力や読み上げ時のミスを減らす設計です。
UUIDと異なり、ULIDにはハイフンが含まれません。UUIDは xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx のような形式ですが、ULIDは 0123456789ABCDEFGHJKMNPQRSTVWXYZ の32文字からなる26文字の連続した文字列です。これにより、コピー&ペーストが容易になり、URLに埋め込んでもエスケープ不要です。実際、ULIDに使われる文字はすべてRFC 3986で「 unreserved characters」と定義されており、URLのパスやクエリパラメータにそのまま使用できます。
UUID v4 / v7 との比較:長さ、ソート可能性、表現
UUIDとULIDの主要な違いは以下の表の通りです。
| 項目 | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| 文字数 | 36(ハイフン込み) | 36(ハイフン込み) | 26(ハイフンなし) |
| ビット数 | 128 | 128 | 128 |
| タイムスタンプ | なし(完全ランダム) | あり(ミリ秒精度、48ビット) | あり(ミリ秒精度、48ビット) |
| ランダム部分 | 122ビット | 74ビット | 80ビット |
| ソート可能性 | なし | 時間順にソート可能 | 時間順にソート可能 |
| 大文字小文字 | 通常大文字で表現されるが、規定なし | 同左 | 規定により大文字小文字を区別しない |
| ハイフン | 5つのグループに分割 | 同左 | なし |
| 使用アルファベット | 16進数(0-9、A-F) | 同左 | Crockfordのbase32(0-9、A-ZからI, L, O, U除外) |
UUID v4は完全にランダムなため、データベースのインデックス(特にB-tree)でランダム挿入が発生し、断片化やパフォーマンス低下を招くことが知られています。一方、ULIDやUUID v7はタイムスタンプが先頭にあるため、新しく生成されるIDはおおよそ昇順になり、B-treeのリーフページへの挿入が効率的です。
UUID v7は2024年にRFC 9562として標準化され、ULIDと似たタイムスタンプ先頭方式を採用しています。しかし、UUID v7は依然として36文字のハイフン付き形式を取るため、ULIDの26文字という短さには及びません。ULIDはUUID v7と比較して約28%短いことになります。
ソート動作と同一ミリ秒内の制限
ULIDは辞書順(文字列としての比較)でソート可能です。0000000000 に近いほど古いタイムスタンプを、7ZZZZZZZZZ に近いほど新しいタイムスタンプを表します。この性質により、データベースのプライマリキーとして使うと、物理的な挿入位置が連続しやすくなります。
ただし注意すべき点があります。同一ミリ秒内で生成された複数のULIDの順序は保証されません。例えば、同じミリ秒に生成された 01ARZ3NDEKTSV4RRFFQ69G5FAV と 01ARZ3NDEKTSV4RRFFQ69G5FAW では、タイムスタンプ部分が同一なので、ランダム部分の大小で順序が決まります。この順序は生成時刻とは無関係です。非常に高いスループットでIDを生成するシステム(例:1ミリ秒に数千件)では、この制限を認識しておく必要があります。
日本語の環境でこのツールを使う場合、I(アイ)、L(エル)、O(オー)、U(ユー)がアルファベットから除外されている点も重要です。これらを使わないことで、手書きや音声での伝達ミスが減ります。ただし、Crockfordのbase32では I は 1 として、O は 0 として解釈することもできるため、実装によっては入力時に柔軟性を持たせることができます。
衝突確率と乱数生成の実際
ULIDのランダム成分は80ビットです。これは約1.2×10^24通りの値を取ります。誕生日のパラダイム(Birthday paradox)を考慮すると、約2^40(約1兆)個のULIDを生成した時点で衝突確率が50%に達します。実際には、生成数が数百万のオーダーであれば衝突確率は無視できるほど小さいと言えます。
このツールはブラウザ上で動作し、crypto.getRandomValues() を使用して乱数を生成します。これはブラウザが提供する暗号論的乱数生成器(CSPRNG)であり、予測不可能性が高いです。生成されたULIDはサーバーに送信されず、すべてローカルで処理されます。したがって、プライバシー上の懸念はなく、オフライン環境でも動作します。
生成可能な数は1~100の範囲に制限されています。これはユーザーインターフェース上の制約であり、技術的な上限ではありません。100個を超える数が必要な場合は、複数回に分けて生成するか、プログラムで直接ULIDを生成する方法を検討します。
データベースインデックスとURLセーフティの実際
ULIDの最大の実務上の利点は、データベースのプライマリキーとしての振る舞いです。ランダムなUUID v4をプライマリキーにすると、B-treeインデックスはランダムな位置に新しいエントリを挿入するため、ページ分割が頻発し、挿入性能が低下します。一方、ULIDはタイムスタンプ順にソートされるため、新しい行は常に最後尾付近に挿入され、インデックスのメンテナンスが効率的になります。
特にPostgreSQLやMySQLでプライマリキーにULIDを使う場合、UUID v4と比較して顕著な書き込み性能の向上が期待できます。実際のベンチマークでは、挿入速度が2倍以上になるケースもあります。
URLセーフティの点では、ULIDの文字セットはすべてURL unreserved文字(RFC 3986)に含まれます。つまり、ULIDをURLのパスやクエリパラメータに含めても、パーセントエンコーディング(%xx形式への変換)は一切不要です。例えば、https://example.com/resource/01ARZ3NDEKTSV4RRFFQ69G5FAV のようにそのまま使えます。これは、UUIDのハイフンがURL内で問題にならないのと同様ですが、ULIDはさらに文字数が少ないという利点があります。
よくある質問(FAQ)
Q: ULIDとUUIDのどちらを使うべきですか?
A: 識別子を生成時刻でソートする必要がある場合、または文字数を減らしたい場合はULIDが適しています。完全なランダム性が必要で、ソート可能性が不要な場合はUUID v4でも構いません。UUID v7はULIDと似た特性を持ちますが、36文字と長めです。
Q: 同じミリ秒で複数のULIDを生成した場合、順序はどうなりますか?
A: タイムスタンプ部分が同一なので、ランダム部分の値によって順序が決まります。生成時刻の前後関係は保証されません。厳密な生成順序が必要な場合は、カウンターを組み合わせた別の方式(例:Snowflake)を検討してください。
Q: ULIDの大文字と小文字はどう扱われますか?
A: Crockfordのbase32の仕様により、大文字と小文字は同一視されます。ツールで生成されるULIDは通常大文字ですが、小文字で入力しても同じ値として解釈できます。
Q: 生成できる数の上限はなぜ100ですか?
A: このツールのUI上の制限です。一度に大量のIDを生成すると操作性が低下するため、1~100の範囲に設定しています。プログラムで大量生成が必要な場合は、専用ライブラリを使用してください。
Q: ULIDはURLでエスケープが必要ですか?
A: いいえ。ULIDで使われる文字(0-9、A-ZからI、L、O、Uを除く)はすべてURL unreserved文字のため、パーセントエンコーディングは不要です。
Q: 生成したULIDの衝突確率はどのくらいですか?
A: 80ビットのランダム成分を持つため、1秒間に100万個のULIDを生成し続けたとしても、衝突が発生するまでに約1兆秒(約3万2千年)かかる計算になります。実用上、衝突を心配する必要はまずありません。