これはなにをしたくて書いたもの?
前にJavaでUUIDが扱えるライブラリーを調べてみました。
JavaでUUIDを扱えるライブラリーを調べる - CLOVER🍀
この時はUUID バージョン6〜8はドラフト段階だったのですが、2024年5月にRFC 9562として公開されました。
RFC 9562: Universally Unique IDentifiers (UUIDs)
今回はPythonでUUID v7を試してみたいと思います。またULIDも扱ってみましょう。
UUID バージョン6〜8
先にも書いたとおり、バージョン6〜8のUUIDはドラフト段階が続いていたのですが、RFC 9562として公開されています。
RFC 9562: Universally Unique IDentifiers (UUIDs)
GitHub - uuid6/uuid6-ietf-draft: Next Generation UUID Formats
こちらに移っています。
GitHub - ietf-wg-uuidrev/rfc4122bis: revision to RFC4122
この中で、比較的使うのではないかと思われるUUID バージョン7に焦点を当てます。
UUID バージョン7は、Unixエポックタイムスタンプから派生したフィールド持ち、バージョン1〜6よりエントロピーを改善したものです。
48ビットにミリ秒のタイムスタンプを割り当て、残りの74ビットを乱数で埋めています。
簡単に言うと、ソート可能なUUIDです。
ちなみにUUID バージョン6もソート可能なUUIDなのですが、構成自体はバージョン1と同じで元にするデータにMACアドレスが含まれています。
ULID
ULIDも挙げておきましょう。ULIDもソート可能でユニークなIDです。仕様は以下で公開されています。
GitHub - ulid/spec: The canonical spec for ulid
Javaでは以下のエントリーで扱ったことがあります。
JavaでULIDを使いたい(Sulky ULIDを使う) - CLOVER🍀
PythonでUUID バージョン7、ULIDを扱うには?
PythonでUUID バージョン7、ULIDを扱えるライブラリーを調べてみました。選択肢はいくつかあるようです。
ざっと見ただけでもこれくらいはありました。
UUID バージョン7。
GitHub - oittaa/uuid6-python: New time-based UUID formats which are suited for use as a database key
uuid6-pythonは名前こそUUID6ですが、UUID バージョン6〜8までを利用できます。
ちなみに、RFC 9562となったことでPythonの標準ライブラリーとしてのUUID バージョン7についてのPull Requestもあったりします。
そのうち標準で扱えるといいですね。
Support UUIDv6, UUIDv7, and UUIDv8 from RFC 9562 · Issue #89083 · python/cpython · GitHub
ULID。
GitHub - mdomke/python-ulid: ULID implementation for Python
GitHub - bdraco/ulid-transform: Create and transform ULIDs
GitHub - ahawker/ulid: Universally Unique Lexicographically Sortable Identifier (ULID) in Python 3
python-ulidはWebサイトもあります。
さてどうしましょうということところですが、今回はUUID バージョン7はuuid6-python、ULIDはpython-ulidを使ってみることにしましょう。
環境
今回の環境はこちら。
$ python3 --version Python 3.12.3 $ pip3 --version pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)
uuid6-pythonでUUID バージョン7を扱う
まずはUUID バージョン7から。
$ pip3 install uuid6
確認はテストコードで行うことにします。型チェックもできるようにしておきます。
$ pip3 install pytest mypy
インストールしたライブラリーの一覧。
$ pip3 list Package Version ----------------- --------- iniconfig 2.0.0 mypy 1.13.0 mypy-extensions 1.0.0 packaging 24.2 pip 24.0 pluggy 1.5.0 pytest 8.3.3 typing_extensions 4.12.2 uuid6 2024.7.10
比較のために、標準ライブラリーに含まれるUUID バージョン4も使っておきましょう。
uuid --- RFC 4122 に従った UUID オブジェクト — Python 3.12.7 ドキュメント
テストコードはこちら。
tests/test_uuid_v4.py
import re import time import uuid def test_uuid_v4() -> None: generated_uuid = uuid.uuid4() assert re.match(r"^[^-]+-[^-]+-4[^-]+-[^-]+-[^-]+$", str(generated_uuid)) != None print(f"generated uuid v4 = {str(generated_uuid)}") def test_generated_many_ids() -> None: generate_size = 10000000 results = set() start = time.time() for i in range(generate_size): results.add(str(uuid.uuid4())) end = time.time() assert len(results) == generate_size print(f"uuid v4 {generate_size} generated, elapsed time = {end - start} sec")
uuid#uuid4
でUUID バージョン4を得ることができます。文字列にする時はstr
を使います。
generated_uuid = uuid.uuid4() print(f"generated uuid v4 = {str(generated_uuid)}")
print
での結果の例。
generated uuid v4 = a7ec59f6-0346-4b71-9443-e8d3813287ae
UUIDは、-
で区切られた3ブロック目の先頭の文字でバージョンを見分けることができます。バージョン4なので4ですね。
assert re.match(r"^[^-]+-[^-]+-4[^-]+-[^-]+-[^-]+$", str(generated_uuid)) != None
続いてuuid6-pythonを使ってみます。
tests/test_uuid_v7.py
import re import time import uuid6 def test_uuid_v7() -> None: generated_uuid = uuid6.uuid7() assert re.match(r"^[^-]+-[^-]+-7[^-]+-[^-]+-[^-]+$", str(generated_uuid)) != None print(f"generated uuid v7 = {str(generated_uuid)}") def test_generated_many_ids() -> None: generate_size = 10000000 results = set() start = time.time() for i in range(generate_size): results.add(str(uuid6.uuid7())) end = time.time() assert len(results) == generate_size print(f"uuid v7 {generate_size} generated, elapsed time = {end - start} sec")
使い方はuuid6#uuid7
を呼び出すだけです。あとは標準ライブラリーのUUID バージョン4と同じですね。
generated_uuid = uuid6.uuid7()
バージョン7なので、3ブロック目の先頭の文字は7です。
assert re.match(r"^[^-]+-[^-]+-7[^-]+-[^-]+-[^-]+$", str(generated_uuid)) != None
print
での結果の例。
generated uuid v7 = 0193810a-5b77-7fb6-9852-7313f39a37c1
簡単に行っているベンチマークの結果を含めたテスト結果の出力。
$ pytest --capture=no =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0 rootdir: /path/to/project collected 4 items tests/test_uuid_v4.py generated uuid v4 = 7a00b640-4ff0-4f61-9c60-1b855148519d .uuid v4 10000000 generated, elapsed time = 40.72287321090698 sec . tests/test_uuid_v7.py generated uuid v7 = 01938111-039c-7bd5-ad00-4417204cd58a .uuid v7 10000000 generated, elapsed time = 55.693116664886475 sec . ======================================================================= 4 passed in 98.42s (0:01:38) =======================================================================
ちなみにuuid6-pythonでもベンチマークを載せていて、こちらでもUUID バージョン7はUUID バージョン4よりは遅くなっています。
1番速いのはUUID バージョン1ですね。
python-ulidでULIDを使う
次は、python-ulidでULIDを使ってみましょう。
$ pip3 install python-ulid ## Pydaticのサポートも追加したい場合 $ pip3 install python-ulid[pydantic]
確認はテストコードで行うことにします。型チェックもできるようにしておきます。
$ pip3 install pytest mypy
インストールしたライブラリーの一覧。
$ pip3 list Package Version ----------------- ------- iniconfig 2.0.0 mypy 1.13.0 mypy-extensions 1.0.0 packaging 24.2 pip 24.0 pluggy 1.5.0 pytest 8.3.3 python-ulid 3.0.0 typing_extensions 4.12.2
テストコードはこちら。
tests/test_ulid.py
import re import time from ulid import ULID def test_ulid() -> None: generated_ulid = ULID() assert len(str(generated_ulid)) == 26 print(f"generated ulid = {str(generated_ulid)}") def test_generated_many_ids() -> None: generate_size = 10000000 results = set() start = time.time() for i in range(generate_size): results.add(str(ULID())) end = time.time() assert len(results) == generate_size print(f"ulid {generate_size} generated, elapsed time = {end - start} sec")
ULID
のコンストラクタでULIDを生成します。
generated_ulid = ULID()
他にもタイムスタンプやdatetime
を元に生成する方法もありますが、省略。
ULIDは26文字で、文字列で扱うにはstr
で変換するのはUUIDと同じですね。
assert len(str(generated_ulid)) == 26
print
での出力例。
generated ulid = 01JE0J35QSK5JNA9ZH3VAQH12P
簡単なベンチマークを含めた実行例。
$ pytest --capture=no =========================================================================== test session starts ============================================================================ platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0 rootdir: /path/to/project collected 2 items tests/test_ulid.py generated ulid = 01JE0J765V9TS6ZHMP8ADECQAM .ulid 10000000 generated, elapsed time = 69.83835077285767 sec . ======================================================================= 2 passed in 70.93s (0:01:10) =======================================================================
こんなところでしょうか。
おわりに
PythonでUUID バージョン7、ULIDを扱えるライブラリーを調べてみました。
Pythonではどのようなライブラリーがあるのか知らなかったのと、RFC 9562になったことでPythonの標準ライブラリーでも追加された
UUIDバージョンの取り込みが進んでいそうなことを知れてよかったですね。