03 / ガイド
UUID vs ULID:アプリケーションの識別子を選ぶ
UUID v4 と v7、ULID の構造とソート可能性、ランダム識別子が B ツリーインデックスに与える影響、衝突確率、URL での露出、そしてローカルでの識別子生成。
アプリケーション識別子に求められること
生成される識別子の本業は一意であること一つで、そこにいくつかの副業がぶら下がります。調整なしにどこでも生成できること、コンパクトに保存できること、予測どおりにソートできること、気まずい情報を漏らさないこと。UUID と ULID はどちらも一意性の関門を通過します。違いはその後のすべてです。
- 調整不要分散生成:どのサービス、ワーカー、ブラウザータブでも、中央カウンターに尋ねずに識別子を鋳造できます。両フォーマットが分散システムに合う理由はこれです。
- 同じ 128 ビットどちらも 128 ビット:UUID はハイフン付き 36 桁の十六進文字で書かれる 128 ビット値、ULID は同じ 128 ビットを 26 文字の base32 に収めます。
- レイアウトが本題本当の問いはレイアウトです。どのビットがランダムで、どのビットが時刻を符号化するかが、インデックス内での識別子の振る舞いと、それが明かすものを決めます。
UUID のバージョン:v4 はランダム、v7 は時系列
RFC 9562 はいくつかの UUID バージョンを定義していますが、アプリケーション開発で支配的なのは二つです。バージョン 4 は 128 ビットのうち 122 ビットをランダムで埋め、他には何も埋め込みません。バージョン 7 は 48 ビットの Unix ミリ秒タイムスタンプで始まり、残りの 74 ビットをランダムで埋めます。
- v4:純粋ランダムv4 はプライバシー最大化の選択です。値はいつどこで作られたかを何も語らず、122 のランダムビットはどんなアプリケーションにも有り余る一意性です。
- v7:時刻が先頭v7 は作成時刻でミリ秒精度にソートされるため、後に生成された識別子は先のものの後に並びます。データベースが主キーに求める性質です。
- v1 は避ける古いバージョンはレガシーです。v1 は MAC アドレスとクロック値を埋め込み、両方を漏らしました。v3 と v5 は安定した ID を導くための名前ベースのハッシュであり、新鮮なランダムではありません。
ULID:テキストのままソート可能でコンパクト
ULID は Crockford base32 の 26 文字です。先頭の 10 文字が 48 ビットのミリ秒タイムスタンプを符号化し、残りの 16 文字が 80 のランダムビットを符号化します。アルファベットは I、L、O、U を意図的に除外しているため、識別子は読み上げやスクリーンショットからの手入力に耐えます。
- 文字列ソート=時系列辞書順がそのまま時系列:ULID をプレーンな文字列としてソートすれば、パースなしで作成時刻順になります。ログ行、ファイル名、キーバリューストアで便利です。
- 26 の安全文字コンパクトで URL セーフ:大文字小文字を区別しない 26 文字で、ハイフンも記号もなく、UUID の 36 文字より短く、どんなパスセグメントやクエリパラメータにも安全です。
- ミリ秒単位で単調同一ミリ秒内では、順序はランダム部分から来ます。Toolars のバッチジェネレーターのようにそれを単調増分させるジェネレーターは、同ミリ秒の識別子さえ生成順に保ちます。
ランダム ID はインデックスを断片化させる
B ツリーインデックスは順序付きであり、ランダムな UUID v4 の挿入は毎回ランダムなリーフへの書き込みになります。ページは分裂し、バッファーキャッシュは揺れ、インデックスは死んだ空間で膨らみます。時系列の識別子はインデックスの右端近くに追加され、挿入を B ツリーの最良ケースに変えます。
だからこそ、この選択は最初の遅い四半期の後ではなくスキーマ設計時に下すべきなのです。主キーの移行は、全行とそれを参照する全外部キーの書き直しを意味します。テーブルが小さいままなら v4 の無秩序は姿を見せません。絶え間ない書き込みで数億行に育つなら、順序付きフォーマットは初日から元を取ります。
- ランダムは散らばるv4 を主キーにした挿入の多いテーブルでは、同サイズの順序付きキーと比べてページ分裂が増え、充填率が下がり、書き込みスループットが測定できるほど悪化します。
- 順序付きは追記v7 と ULID は生成を分散したまま局所性を取り戻します。シーケンスも調整も不要で、他の行の ID の予測可能性も依然ありません。
- トレードを測るトレードは現実ですが控えめです。128 ビットは bigint の倍のストレージで、時系列キーは当日の書き込みをインデックスの右端に集中させますが、それが問題になるのは非常に高い挿入レートのときだけです。
衝突、URL、そして ID が明かすもの
まず衝突の数学です。v4 の 122 ランダムビットでは、10 億個の識別子を生成しても衝突確率は 10^18 分の 1 程度——実質的に起きません。ULID の 80 ランダムビットが守るのは別の予算です。同じミリ秒内に作られる識別子です。
怪しい値はバリデーターに貼り付けてください。RFC 9562 のバリアントとバージョンニブルを持つ正準の 36 文字 UUID 形状、オーバーフロールール付きの 26 文字 ULID アルファベットを認識し、時系列フォーマットでは埋め込みタイムスタンプを報告します。ログの中の値が、それを基に何かを作る前に、何であるかを教えてくれます。
- 確率は無視できるどちらのフォーマットもセキュリティ境界ではありません。URL 内の識別子はアドレッシングには問題ありませんが、見た人は誰でもそれを引用できます。認可は ID の不透明性ではなくアクセスチェックから来るべきです。
- ID は秘密ではないv7 と ULID は設計上、作成時刻を漏らします。バリデーターはそれを公然と抽出します。通常は無害なメタデータですが、公開されるオブジェクトでは意識的に決めましょう。
- 時刻は見える連番のデータベース ID は件数と成長率を漏らします。ランダムおよび時刻+ランダムの識別子は、レコードが何件あるかを何も明かしません。
サーバーなしで生成と検証をする
識別子の生成に必要な希少資源は良質なランダム性ただ一つで、ブラウザーはすでにそれを持っています。作業領域は crypto.getRandomValues から取り出し、バッチ全体をタブ内で生成し、値を一切送信しません。
- 1 バッチ 1〜100UUID v4、UUID v7、ULID を選び、1 バッチ 1〜100 個を生成します。v7 と ULID のバッチはランダムフィールドを単調増分するので、バッチ全体が生成順を保ちます。
- 大小文字とエクスポート出力の大小文字は表示上の選択です。正準、大文字、小文字。一括コピーに加え、改行区切りテキストと、各識別子の埋め込み時刻を記録した CSV のダウンロードがあります。
- 即時検証検証は構造の問いに即答します。長さ、アルファベット、UUID のバージョンとバリアント、nil と max の特殊値、ULID のオーバーフロー、そして UTC の埋め込みタイムスタンプです。
ULID は base32、あなたのトークンは多分 Base64 です。
バイトからテキストへのエンコーディングは、あらゆる識別子とトークンの下に潜んでいます。そのコストと、使う価値のある場所を学びましょう。