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

Transcript

53 slides
  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

More Decks by Yoshiori SHOJI

Other Decks in Programming