kakakakakku blog

Weekly Tech Blog: Keep on Learning!

UUID v6, v7, v8 : タイムスタンプでソートできる新しい UUID のドラフト仕様

ID を採番するときによく使われる UUID Version 4 の課題として「順序性がなくソートしにくい」という側面があり,ULID (Universally Unique Lexicographically Sortable Identifier) を使えばソートできるようになるという記事を前に書いた.

kakakakakku.hatenablog.com

関連して調査をしていたら,標準化団体 IETF (Internet Engineering Task Force) によって,UUID Version 6, 7, 8 という新しい仕様が提案(ドラフト段階)されていることを発見した❗️UUID Version 6, 7, 8 の目的を簡単にまとめると「タイムスタンプ情報を使ってソートできる ID を採番できるようにする」となり,もしこの仕様が取り込まれると,UUID を活用する幅がさらに広がりそう.

  • UUID Version v6: グレゴリオ暦ベース(UUID Version v1 の改善)
  • UUID Version v7: Unix Time Stamp ベース
  • UUID Version v8: 独自仕様(実験的もしくはベンダー固有の要件で使う)

詳細な仕様は以下に載っている.記事を書いている2022年10月時点ではまだドラフト段階なので,変わる可能性はある!

www.ietf.org

github.com

uuid6/prototypes リポジトリ

GitHub の uuid6/prototypes リポジトリを見ると,いろいろな言語で実装された UUID Version 6, 7, 8 用のライブラリが載っている.

github.com

今回は UUID Version v6UUID Version v7 を試すため,Pyhon で最新ドラフト (04) までをサポートしている uuid6 を使う.pip で簡単にセットアップできる.なお,名前は uuid6 だけど,実装としては UUID Version v6 と UUID Version v7 どちらも試せる.

github.com

UUID Version 6 (UUID v6)

ドラフト仕様を読むと,UUID v6UUID v1 を改善するために策定された仕様で,UUID v1 のフィールドの構成を見直したものになる.UUID v1 は「タイムスタンプ情報」を持つけど,最下位部分と上位部分が逆転していて,ソートはできない.UUID v6 はあくまで UUID v1 の課題を解決したものとなり,基本的には UUID v7 を使うとドラフト仕様に書いてある.

UUID v6 のタイムスタンプ情報は UUID v1 と同じく「グレゴリオ暦ベース (60 bit)」で,1582年10月15日からの 100 ns 単位のカウントとなる.記事を書きながら採番した UUID v6 の例を以下に載せておく.

  • 1ed575d3-b55c-6f9c-93a7-e81a4645882c
  • 1ed575d3-c876-609c-9270-7532b69c8a8e
  • 1ed575d3-db8a-669c-8634-8a0ba964ffa3

UUID Version 1 is a time-based UUID featuring a 60-bit timestamp represented by Coordinated Universal Time (UTC) as a count of 100- nanosecond intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian reform to the Christian calendar).

UUID v6 のフィールド構成は 128 bit 長で以下の通り.より詳細な表現はドラフト仕様を参照で!なお,ID の中に「バージョン情報」も含まれるため,3 ブロック目の1文字目は必ず 6 になる.

  • time_high: タイムスタンプ情報の最上位 32 bit
  • time_mid: タイムスタンプ情報の中間 16 bit
  • time_low_and_version: バージョン情報 4 bit とタイムスタンプ情報の最下位 12 bit
  • clk_seq_hi_res: UUID variant 10 2 bit とクロックシーケンス情報の上位 6 bit など
  • clk_seq_low: クロックシーケンス情報の下位 8 bit
  • node: 一意の識別子 48 bit
 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            |      time_low_and_version     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         node (2-5)                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

UUID v6 を Python で試す

uuid6 ライブラリを使うと簡単に UUID v6 で採番できる.以下では2秒間隔で「3種類」の id を採番して,期待通りに比較できている.比較できるということはソートもできる!

from uuid6 import uuid6
import time

# 1ed575d3-b55c-6f9c-93a7-e81a4645882c
id1 = uuid6()
print(id1)

time.sleep(2)

# 1ed575d3-c876-609c-9270-7532b69c8a8e
id2 = uuid6()
print(id2)

time.sleep(2)

# 1ed575d3-db8a-669c-8634-8a0ba964ffa3
id3 = uuid6()
print(id3)

# 比較
print(id1 < id2)  # True
print(id2 < id3)  # True
print(id3 < id1)  # False

UUID Version 7 (UUID v7)

ドラフト仕様を読むと,UUID v7 はよく使われる Unix Time (32 bit) にミリ秒を追加した 48 bit を使う. 記事を書きながら採番した UUID v7 の例を以下に載せておく.

  • 018422b2-4843-7a62-935b-b4e65649de3e
  • 018422b2-5013-7f02-830f-2563ce4533df
  • 018422b2-57e8-79b2-8b59-d6926217a9dc

UUID v7 のフィールド構成も 128 bit 長で以下の通り.より詳細な表現はドラフト仕様を参照で!なお,ID の中に「バージョン情報」も含まれるため,3 ブロック目の1文字目は必ず 7 になる.

  • unix_ts_ms: タイムスタンプ情報 48 bit
  • ver: バージョン情報 4 bit
  • rand_a: 乱数 12 bit
  • var: variant 2 bit
  • rand_b: 乱数 62 bit
 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                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

UUID v7 を Python で試す

uuid6 ライブラリを使うと簡単に UUID v7 で採番できる.以下では2秒間隔で「3種類」の id を採番して,期待通りに比較できている.比較できるということはソートもできる!

from uuid6 import uuid7
import time

# 018422b2-4843-7a62-935b-b4e65649de3e
id1 = uuid7()
print(id1)

time.sleep(2)

# 018422b2-5013-7f02-830f-2563ce4533df
id2 = uuid7()
print(id2)

time.sleep(2)

# 018422b2-57e8-79b2-8b59-d6926217a9dc
id3 = uuid7()
print(id3)

print(id1 < id2)  # True
print(id2 < id3)  # True
print(id3 < id1)  # False

UUID Version 8 (UUID v8)

ドラフト仕様を読むと,UUID v8 は限定的な用途のために策定されていて,正確には「実験的」もしくは「ベンダー固有」と書いてある.UUID v8 のフィールド構成も 128 bit 長だけど,UUID v7 と同じく vervar は必須で,他は自由に設定できる.1点注意点としては,ドラフト仕様に「ランダムで埋めちゃダメ」と書いてあるので,あくまで独自仕様のタイムスタンプ情報を含めることが前提になりそう.例えば「何かしらの理由でタイムスタンプを知られたくない」というときに使えそう?

To be clear: UUIDv8 is not a replacement for UUIDv4 where all 122 extra bits are filled with random data.

  • custom_a: タイムスタンプ情報 48 bit
  • ver: バージョン情報 4 bit
  • custom_b: 乱数 12 bit
  • var: variant 2 bit
  • custom_c: 乱数 62 bit
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           custom_a                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          custom_a             |  ver  |       custom_b        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                       custom_c                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           custom_c                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+