アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

タイムスタンプでソートできるUUID の仕様が標準化されました

こんにちは。プロダクト開発グループの斉藤です。

UUIDの新しい仕様が、2024年5月に正式に標準化され、RFC 9562 として採番されました。タイムスタンプ情報を含み、生成された時刻でソートが可能な仕様が含まれます。

従来の RFC 4122 に加えて、新たにv6、v7、v8の3つのバージョンが追加された形になります。

現状で UUID を使うとした時に「どのバージョンを使うか」ということが議論になることはあまり無いかと思います。一般的に使われないバージョンがいくつもあるわけです。

今回の記事では、新しく追加された仕様を中心に、全てのバージョンを簡単に紹介しようと思います。

UUID (Universally Unique IDentifier) とは?

中央(サーバ)で管理をせずに一意の識別子を生成するための技術です。UUIDは128ビットの固定長であり、通常は8-4-4-4-12の形式で表現されます。例えば、b90972de-553f-4b17-8ebd-85b79086bad7のような形式です。

UUIDの生成には複数のバージョンがあり、それぞれ異なる方法で識別子を生成します。

以下に、RFC 4122 の範囲の version 1 から version 5 までを簡単に説明します。

現在、一般的に利用されているのは version 4 です。

また、version 1 が version 6 以降の仕様に深く関わりがあるため、少し細かく説明をします。

従来のUUID (RFC 4122)

version 1 (タイムスタンプ + ノードID)

主にタイムスタンプ(UUID の生成時刻)と、ノードIDが使われるUUID です。ノードIDには通常はMACアドレスが使用されます。

タイムスタンプは、グレゴリオ暦の開始日(1582/10/15)からの経過時間を使います。

冒頭で「タイムスタンプでソートできるUUIDが追加された」と書きましたが、タイムスタンプが含まれる仕様自体はそもそもあるわけです。

なぜこの v1がソート出来ないかは、バイナリ構成をみるとわかります。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           time_low                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           time_mid            |  ver  |       time_high       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|         clock_seq         |             node              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              node                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

time_low, time_mid, time_high の3つがタイムスタンプの値を分割したものですが、タイムスタンプを high, mid, low の順に分割した後に、その逆順で結合しています。

そのため、v1 はタイムスタンプは含んでいますが、素直に辞書順にソートすることはできなくなっています。

また、MACアドレスが含まれる点も問題視されています。MACアドレスが含まれることでUUIDから生成元のデバイスが特定できてしまいます。そのため仕様にセキュリティ上の懸念があるとされ、後述の version 4 が一般的に利用されるようになりました。

version 2 (DCE セキュリティ用)

DCEアプリケーションで主に利用されるバージョンで RFC での定義には含まれません。事実上の非標準扱いで、一般的には利用されません。

version 3 (MD5)

特定の名前に基づいて UUID を生成するバージョンです。特定の名前をMD5でハッシュ化し、ハッシュ値をUUID に組み入れます。

ハッシュ化する値が同じであれば常に同じUUIDが生成されます。v3 は特定の値に対して一貫した識別子を生成したい場合に利用されます。

version4 (ランダム)

ランダムで値を生成します。タイムスタンプやMACアドレスなどに依存せず、単純で高速に生成できるという利点があります。

現在、広く一般的に使われているバージョンで、特に注釈なくUUID といった場合は v4 を指していると思って良いです。

version 5 (SHA-1)

v3 と同じですが、こちらは SHA-1 でハッシュ化を行います。

新しいUUID (RFC 9562 で追加)

従来の仕様は生成されたUUIDができるだけ分散することが意識されていたようです。これは、順序性がある値が生成されることでホットスポットの問題が発生することを回避することを意図としています。

その代わり、DBの主キーとしてUUIDを利用すると、データベースインデックスが有効に使えず検索効率が悪くなってしまっていました。

その対応のために定義されたのが v6 と v7 です。

version 6

従来のUUID v1を改良したバージョンです。タイムスタンプを基に一意の識別子を生成しますが、v1 のようにタイムスタンプ情報の入れ替えをやめ、ソートができるようにしています。タイムスタンプは v1 から引き続きグレゴリオ暦を使用します。

バイナリ構成は以下のようになっています。 v1 と比較すると定義としては time_high と time_low の位置が変わっただけになっています。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           time_high                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           time_mid            |  ver  |       time_low        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|         clock_seq         |             node              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              node                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

ノードID(node) とクロックシーケンス(clock_seq) は、その位置は変更されないままですが、”UUIDを生成するごとにランダム値でリセットされるべき[SHOULD]” と定義されており、ランダム値を使用することが推奨されます。ランダムが必須になっていないのは v1 との互換のためのようです。

タイムスタンプの順序の入れ替えをやめたことで、time_high と time_mid はそもそも分割する必要がなくなりました。それでも定義上は別になっているのは v1 の実装を流用する場合への配慮のようです。”time_high と time_mid の分割はオプションで、この余分なステップはv1の実装を流用する場合に便利です” という記述がRFCにあります。

version 7

v6 を更に進化させたバージョンです。タイムスタンプは従来のグレゴリオ暦ではなく、UNIXタイムを採用しています。また、v1との互換のために v6 に残っていたノードID やクロックシーケンスの定義がなくなり、ランダム値を使用します。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

これらの変更により、v1 にあったセキュリティ上の懸念なども解決されます。

v1 との互換を必要としない場合においては v6 よりも v7 の利用が推奨されます。

version 8

実験やベンダー固有の実装のための定義です。通常は使用しません。

まとめ

一通りのバージョンの説明をしましたが、v6 以降の新しいバージョンも従来の使用の上位互換というわけではありません。どのバージョンを採用するかは、プロジェクトの要件に応じて適切なバージョンを選択する必要があります。

ランダム性が重要であれぱ依然として v4 が適しています。

データべースのインデックスを有効に使いたい場合などは v7 が適しているでしょう。

今の時点では、各言語の標準の実装に v6 や v7 が含まれているケースは少ないと思います。そのため v4 が一番手軽に使えるバージョンとして、まだ使われ続けると思われます。

プログラミング言語やフレームワークが v7 のサポートを追加していくにつれて v4 から v7 へシフトしていくのではないかと思います。