ユニークな ID を採番するときに UUID (Universally Unique Identifier) v4 を使う場面は多いと思う.しかし要件によっては UUID だと「順序性がなくソートしにくい」という側面もあったりする.今回はユニーク性を維持しつつミリ秒精度でソートできる ULID (Universally Unique Lexicographically Sortable Identifier) を試す.仕様などの詳細は以下の GitHub に載っている.
ulid-py
ULID ライブラリは多くの言語で実装されている.今回は Python で使えるライブラリ「ulid-py」を試す.
$ pip install ulid-py
基本機能
Python インタプリタを使って ulid-py の基本機能を試す.ulid-py では ulid.new()
を使って ID を採番できる.そして .str
を使うと文字列として取得できる.
>>> import ulid >>> id = ulid.new() >>> id <ULID('01GF2EB9Z7G7K2D6F1ZWJA9913')> >>> id.str '01GF2EB9Z7G7K2D6F1ZWJA9913'
ULID の ID フォーマットは以下のように Timestamp(タイムスタンプ)
と Randomness(ランダム)
で構成される.
01GF2EB9Z7 G7K2D6F1ZWJA9913 |----------| |----------------| Timestamp Randomness 48bits 80bits
timestamp().str
を使うと Timestamp
を文字列として取得できる.さらに .timestamp().datetime
を使うと Python の datetime
型で日付を取得できる.
>>> id.timestamp() <Timestamp('01GF2EB9Z7')> >>> id.timestamp().str '01GF2EB9Z7' >>> id.timestamp().int 1665455728615 >>> id.timestamp().datetime datetime.datetime(2022, 10, 11, 2, 35, 28, 615000, tzinfo=datetime.timezone.utc)
さらに .randomness().str
を使うと Randomness
を文字列として取得できる.
>>> id.randomness() <Randomness('G7K2D6F1ZWJA9913')> >>> id.randomness().str 'G7K2D6F1ZWJA9913'
id を比較する
以下のように3種類の ID を数秒待ちつつ採番する.期待通りに比較できる.比較できるということはソートもできる.
>>> id1 = ulid.new() >>> id2 = ulid.new() >>> id3 = ulid.new() >>> id1 < id2 True >>> id1 < id3 True >>> id3 < id1 False
同時に採番する
ulid-py では「ミリ秒精度の」タイムスタンプが同じでも Randomness(ランダム)
で ID が重複しないように考慮されている.以下は ulid.from_timestamp()
を使って「特定のタイムスタンプ」から ID を3種類採番している.Timestamp(タイムスタンプ)
は 01FR8G2EC0
で同じになりつつも Randomness(ランダム)
で異なる id を採番できている.
>>> import datetime >>> ulid.from_timestamp(datetime.datetime(2022, 1, 1)) <ULID('01FR8G2EC0643MA59P9WTKXSAY')> >>> ulid.from_timestamp(datetime.datetime(2022, 1, 1)) <ULID('01FR8G2EC0DZR7HD80ARGVG2CM')> >>> ulid.from_timestamp(datetime.datetime(2022, 1, 1)) <ULID('01FR8G2EC0FT0EZXYQDE56R9TV')>
JST でタイムスタンプを取得する
ULID のタイムスタンプを JST に変換する場合は .astimezone()
や dateutil ライブラリを使う.
>>> id.timestamp().datetime.astimezone(datetime.timezone(datetime.timedelta(hours=+9))) datetime.datetime(2022, 10, 11, 11, 35, 28, 615000, tzinfo=datetime.timezone(datetime.timedelta(seconds=32400))) >>> from dateutil.tz import gettz >>> id.timestamp().datetime.astimezone(gettz('Asia/Tokyo')) datetime.datetime(2022, 10, 11, 11, 35, 28, 615000, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Tokyo'))