Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ソートできるUUID v7をJavaで使うときの話

ソートできるUUID v7をJavaで使うときの話

JJUG CCC 2024 Spring の発表資料です

Yoshiori SHOJI

June 16, 2024
Tweet

More Decks by Yoshiori SHOJI

Other Decks in Programming

Transcript

  1. UUIDって何 Universally Unique Identifier (UUID) ◦ f81d4fae-7dec-11d0-a765-00a0c91e6bf6 ▫ こういうやつ ▫

    固定サイズ (128 ビット) ◦ 中央ID管理(ID生成機構)が必要無い ▫ DBのauto incrementとか ▫ 古くはTwitterのsnowflakeとか ◦ 生成方法は複数定義されている 11
  2. RFC4122 version 3: 何かしらを元にMD5ハッシュ値で生成 12 version 2: DCEセキュリティUUID 使わない version

    1: タイムスタンプ+クロックシーケンス+ノードID version 4: 完全乱数 version 5: 何かしらを元にSHA-1ハッシュ値で生成
  3. RFC4122 version 3: 何かしらを元にMD5ハッシュ値で生成 14 version 2: DCEセキュリティUUID 使わない version

    1: タイムスタンプ+クロックシーケンス+ノードID version 4: 完全乱数 version 5: 何かしらを元にSHA-1ハッシュ値で生成 あるじゃん
  4. UUID version 1: タイムスタンプ+クロックシーケンス+ノードID UUID version 1 生成方法 ◦ 現在時刻(100ナノ秒単位)

    ▫ 1582年10月15日(グレゴリオ暦開始日)からの経過時間 ▫ それを3つに分割するtime_hi,time_mid,time_low ▫ そしてそれをtime_low,time_mid,time_hi(&version)の順で結合 ◦ クロックシーケンス ◦ ノードID(通常はMACアドレスを使用) 15
  5. UUID version 1: タイムスタンプ+クロックシーケンス+ノードID UUID version 1 生成方法 ◦ 現在時刻(100ナノ秒単位)

    ▫ 1582年10月15日(グレゴリオ暦開始日)からの経過時間 ▫ それを3つに分割するtime_hi,time_mid,time_low ▫ そしてそれをtime_low,time_mid,time_hi(&version)の順で結合 ◦ クロックシーケンス ◦ ノードID(通常はMACアドレスを使用) 16 つまり雑にいうと上下逆にして る
  6. UUID version 1: タイムスタンプ+クロックシーケンス+ノードID time_low,time_mid,time_hi(&version)の順で結合 ◦ コードで書くと... ▫ ((timestamp &

    0x0FFFL) << 48) | ((timestamp & 0x1FFFL) << 16) | ((timestamp >> 32) & 0xFFFFL) ◦ time_hi を先頭に持ってくると先頭数バイトはしば らく同じものになってしまう ◦ 分散DBとかのキーにするとHotSpotを作りやすく なってしまう ◦ なのでtime_lowを先頭にしている 17
  7. UUID v1,2,3,4,5 (RFC4122)をとりまく個人的な感想 ◦ 表現形式と生成方式を分けて考える ▫ 表示形式 ▪ 固定サイズ (128

    ビット)を16進数でハイフン区切り ▫ 生成方式 ▪ v1,2,3,4,5 が定義される ▪ すべてなるべく分散することを目的に生成 ▪ v1 • 時間データを持っているが辞書順ではソートできないパースして頑張 ればソートは出来る • しかしノードIDにセキュリティリスクがある ◦ なるべくランダムでしかもノードIDとかを持たないほうが良い 19
  8. RFC4122 version 3: 何かしらを元にMD5ハッシュ値で生成 20 version 2: DCEセキュリティUUID 使わない version

    1: タイムスタンプ+クロックシーケンス+ノードID version 4: 完全乱数 version 5: 何かしらを元にSHA-1ハッシュ値で生成 なのでコレが一般化
  9. Java のUUID ◦ UUID(long mostSigBits, long leastSigBits) ▫ コンストラクタ ▫

    上位ビットと下位ビットの2つのlong ▫ UUID v1 とかは自分で時間から計算して作る ◦ static UUID nameUUIDFromBytes(byte[] name) ▫ v3 が作れるよ ◦ static UUID randomUUID() ▫ v4 が作れるよ ◦ static UUID fromString(String name) ▫ 文字列表現(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx )から生成 22
  10. UUID v6 v1 からの変更点 ◦ ノードIDはセキュリティ的にも無いほうが良いね ◦ クロックシーケンスも無いほうが良いね ▫ 乱数値に(一応元の値を使うことも可能)

    ◦ タイムスタンプを入れ替えるのをやめる ▫ 引き続きグレゴリオ暦ベース ▫ time_low,time_mid,time_high_and_version -> time_high,time_mid,time_low 27
  11. UUID v7,v8 29 v7 一般的によく使われているUNIXタ イムスタンプを使うようになっ た。精度はミリ秒。 あとはランダム値。 (細かくいうとカウンタとかもあ るがそれも生成方法の1つにラン

    ダムが許されているので本当に必 要なとき以外気にしなくて良い) v8 実験的またはベンダー固有の使用 のためのフォーマット。 あんまり気にしなくて良い。
  12. RFC9562 version 8: カスタム定義用 32 version 7: ミリ秒精度のUNIXタイムスタンプベース version 6:

    v1ベース。タイムスタンプの上位ビットを前にした 新規で使うならコレ使っておけば良い
  13. Javaで使うRFC9562 35 表現形式 これはそもそも仕様自体も 何も変更無いのでそのまま使え る。 生成方式 まだ標準APIには入ってないので 外部ライブラリを使う必要があ る。

    - cowtowncoder/java-uuid-generator - f4b6a3/uuid-creator AUTION CAUTION CAUTION CAUTION CAUTIO CAUTION CAUTION CAUT AUTION CAUTION CAUTION CAUTION CAUTI ION CAUTION CAUTION CAUTION CAUTION TIO N C A U TIO N C A U TIO N C A U TIO N
  14. Java のUUID ◦ UUID(long mostSigBits, long leastSigBits) ▫ コンストラクタ ▫

    上位ビットと下位ビットの2つのlong ▫ UUID v1 とかは自分で時間から計算して作る ◦ static UUID nameUUIDFromBytes(byte[] name) ▫ v3 が作れるよ ◦ static UUID randomUUID() ▫ v4 が作れるよ ◦ static UUID fromString(String name) ▫ 文字列から生成 36
  15. UUID(long mostSigBits, long leastSigBits) 内部的にもこれで持っている ◦ 128 bitのデータを持つのにlong(64 bit)x2は理に かなっている

    ◦ なので内部的にはいろいろなところで使われている ◦ 例えばUUIDクラスは順番を持っている ▫ implements Comparable<UUID> ▫ public int compareTo(UUID val) 内でも使用 ▪ ここが問題になる 37
  16. public int compareTo(UUID val) 順番比較 ◦ 上位のlongを比較して上位が完全に一緒だったとき に下位のlongを比較している 38 @Override

    public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers int mostSigBits = Long.compare(this.mostSigBits, val.mostSigBits); return mostSigBits != 0 ? mostSigBits : Long.compare(this.leastSigBits, val.leastSigBits); } https://github.com/openjdk/jdk/blob/d60331a21c30271340f7d6d58f3122f0e6431a04/src/java.base/share/classes/java/util/UUID.java#L557-L562
  17. UUID(long mostSigBits, long leastSigBits) 内部的にもこれで持っている ◦ 128 bitのデータを持つのにlong(64 bit)x2は理に かなっている

    ◦ なので内部的にはいろいろなところで使われている ◦ 順番を持っている implements Comparable<UUID> ◦ public int compareTo(UUID val) 内でも使用 ▫ ここが問題になる 40
  18. 128 bitのデータを持つのにlong(64 bit)x2は理にかなっている 符号の問題 ◦ UUID は符号なし、long(64 bit)は符号あり ◦ つまりある程度大きい値を変換すると内部的には負

    のlongを保持することになる 41 jshell> var max = UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") max ==> ffffffff-ffff-ffff-ffff-ffffffffffff jshell> max.getMostSignificantBits() $2 ==> -1 jshell> max.getLeastSignificantBits() $3 ==> -1 ちなみにUUID MAXもRFC9562 で追加された定数
  19. public int compareTo(UUID val) 順番比較 ◦ 上位のlongを比較して上位が完全に一緒だったとき に下位のlongを比較している 42 @Override

    public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers int mostSigBits = Long.compare(this.mostSigBits, val.mostSigBits); return mostSigBits != 0 ? mostSigBits : Long.compare(this.leastSigBits, val.leastSigBits); } https://github.com/openjdk/jdk/blob/d60331a21c30271340f7d6d58f3122f0e6431a04/src/java.base/share/classes/java/util/UUID.java#L557-L562
  20. 符号の問題 compareTo で問題になる(Sort等々) ◦ 辞書的順番とは異なる順序付けになる 43 jshell> var max =

    UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") max ==> ffffffff-ffff-ffff-ffff-ffffffffffff jshell> var nilUUID = UUID.fromString("00000000-0000-0000-0000-000000000000") nilUUID ==> 00000000-0000-0000-0000-000000000000 jshell> Collections.min(List.of(max, nilUUID)) $3 ==> ffffffff-ffff-ffff-ffff-ffffffffffff
  21. JavaのUUID 背景補足 ◦ JavaのUUIDはまだRFC4122だった時代に作られた ▫ UUIDは辞書的順番に生成されない ▫ むしろ分散しやすいように意識してた ◦ なので

    compareTo の実装はしょうが無い ▫ Long.compareUnsigned 使えばよかったのでは? ▪ Long.compareUnsigned はJava8から(UUIDクラスはJava5) 46
  22. JavaでRFC9562を使う 48 UUID v7 の生成 まだ標準にはなっていないので外 部ライブラリ使う - cowtowncoder/java-uuid-generator -

    f4b6a3/uuid-creator など ソート時はComparator指定する 各種ライブラリに付属しているの でそれを使うと良い。 何も指定しないと辞書順にはソー トできない。
  23. Max UUID ffffffff-ffff-ffff-ffff-ffffffffffff ◦ もともとNil UUIDはRFC4122で定義されてた ▫ 全部0 ◦ 時間順にソートできるよになるから上も欲しいよね

    ◦ 名前の候補が色々あった ▫ OmniUUIDというのもあってこれが面白かった ▫ ラテン語で「全て」という意味らしい ▫ nilがもともとラテン語で「無」を表すのでその反対として候補 に出てた ▫ まぁ、わかりやすいMAXで決まった 50
  24. UUID のバージョン判定方法 2個目のハイフンの後ろがバージョン番号 ◦ 9fa7d6d6-725a-4f92-9c9c-f53b8be7d5bb ▫ この場合は v4 だとわかる ◦

    なので今後UUID.compareToもそれ見て下位互換保つ んじゃないかなぁ ▫ Nil UUID,Max UUIDの処理して ▫ バージョン見て6,7,8だったらLong.compareUnsigned使う ▫ それ以外は今までどおりLong.compare使う 51