kakakakakku blog

Weekly Tech Blog: Keep on Learning!

ソートできるユニークな ID を ULID で採番する

ユニークな ID を採番するときに UUID (Universally Unique Identifier) v4 を使う場面は多いと思う.しかし要件によっては UUID だと「順序性がなくソートしにくい」という側面もあったりする.今回はユニーク性を維持しつつミリ秒精度でソートできる ULID (Universally Unique Lexicographically Sortable Identifier) を試す.仕様などの詳細は以下の GitHub に載っている.

github.com

ulid-py

ULID ライブラリは多くの言語で実装されている.今回は Python で使えるライブラリ「ulid-py」を試す.

$ pip install ulid-py

github.com

基本機能

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'))