【RFC 9562】新しい UUID の概要紹介
目次
先日、UUID の仕様を定める RFC が改訂され、『RFC 9562 : Universally Unique IDentifiers (UUIDs)』として公開されました。 ドラフトの頃から読んでいたものではありますが、改めて読み直したので、従来の UUID や新しい UUID について紹介します。
従来の UUID の概要
UUID は、中央の登録プロセス (central registration process) なしに生成できる ID です。
この性質により、分散システムや多ノードにスケールアウトしたシステムにおける一意な値としてよく利用されています。 また、リレーショナルデータベースのような中央集中型システムにおいても ID として利用することがあります。
従来の RFC 4122 では5種類の UUID が定義されており、次のものから生成されます。
- UUID version 1 : 100ナノ秒単位のタイムスタンプ + 重複排除用の乱数など + ノード名 (MACアドレスなど)
- UUID version 2 : 外部の文書で仕様が定義される (通常利用しないはず)
- UUID version 3 : 何らかの一意な値の MD5 ハッシュ値
- UUID version 4 : 乱数
- UUID version 5 : 何らかの一意な値の SHA-1 ハッシュ値
UUID 自体は128ビットのデータであり、その中にはバージョンを表す4ビットと、2ビット(または3ビット)のバリアントが含まれています。
また、UUID の文字列表記としては f81d4fae-7dec-11d0-a765-00a0c91e6bf6
のような形式で表現することが定められており、2つ目のハイフンの次の文字をみればどのバージョンのものか一目でわかるようになっています。
5種類の UUID の中でも、version 4 はバージョンやバリアントを表すビットを除いた122ビット(または121ビット)の乱数を元に作成されており、適切な乱数生成器を利用すれば ID が衝突する可能性はほぼゼロであるため、たいていのケースで version 4 が利用されているではないでしょうか。
version 1 も一定の範囲で利用されているかと思います。 version 3 や 5 は一意な値が存在することを前提としているため、既にある ID を UUID 形式にする必要がある状況でしか使い道がないようにも思います。
従来の UUID の課題
UUID は扱いやすくて便利なものですが、2005年に標準化されて以降いくつかの課題が見つかっており、RFC 9562 の『2.1. Update Motivation』にも挙げられています。
その中でも1つ目の課題である、UUID は生成された時間順に並ばないという課題はよく挙げられます。 UUID version 3,4,5 は当然として、version 1 もタイムスタンプの下位ビットが UUID の上位ビットに来るよう設計されているため、時間順にソートされません。 そのため、データベースのプライマリーキーとして UUID を利用すると、インデックスの効率が悪化するということがよく言われていました。
この課題の詳細は、次のブログ記事で非常にわかりやすく説明されています。
実際私も2022年に新しくシステムを構築した際、この課題を踏まえて、データベースのプライマリーキーとして UUID ではなく ULID を採用しました。 ULID を採用した理由は後述します。
新しい UUID の概要
2005年に標準化された UUID の課題を解決するため、2020年に新しい UUID に関するドラフトが提出され、ようやく今月に RFC 9562 として標準化されました。 これに伴い、従来の UUID を定めた RFC 4122 は廃止となっています。
RFC 9562 ではこれまでの UUID の仕様は変更しておらず、大きな変更として新しく version 6,7,8 の3種類が定義されました。
- UUID version 6 : 構成は version 1 と同じで、タイムスタンプの上位ビットが UUID の上位ビットに来るようにしたもの
- UUID version 7 : ミリ秒単位のタイムスタンプ (48ビット) + 乱数
- UUID version 8 : 独自定義可能な UUID 用 (具体的な仕様はなく、これを利用する状況や定義は開発者しだい)
version 6 も 7 も生成された時間順に並ぶよう設計されていますので、データベースでの利用における課題が解決されています。
version 7 の乱数部は74ビットであり、version 4 に比べて減少しています。 しかし、1ミリ秒ごとに2の74乗の空間があり、ID が衝突する可能性はほぼゼロですので、version 4 と同様に安心して利用できるものとなっています。
ID 自体に時刻が含んでいることにメリットがあるケースも多いでしょうから、現在 version 4 を利用しているのであれば今後は version 7 を利用するのが良いかと思います。 時刻が含まれていることで弊害がある場合は version 7 の利用は避けると良いです。
ULID との互換性
先述の通り、2022年に私が構築したシステムでは ULID を採用しました。 その当時から新しい UUID のドラフトを読み、ULID と将来新しく定義される UUID が非常に相性の良いものであったことや、UUID よりも効率的に扱えることがわかったため、ULID の採用を決めました。
ULID は UUID と同じく128ビットのデータであり、48ビットのタイムスタンプと80ビットの乱数から成ります。 タイムスタンプ部分が UUID version 7 と同じであり、乱数部もバージョンやバリアントを示すビットを除いてほぼ同じであるため、UUID version 7 を ULID として扱うことができるということでもあります。
また、UUID の文字列表記は16進数の文字とハイフンで構成されるため36文字ありますが、ULID は32種類の文字で構成されるため26文字となっていますので、ID を文字列として保持する際にデータやメモリの節約にも寄与します。 構築したシステムでは ULID の仕様に従って生成した ID を利用していますが、プログラミング言語 (今回のシステムでは Python) の標準ライブラリで UUID version 7 が扱えるようになった際は、それを用いて ID を生成して ULID 形式の文字列で扱うということを視野に入れていました。
なお、ULID で生成した ID を UUID として扱うことはできません。 これは、バージョンやバリアントが UUID の定義に反するからです。