見出し画像

ULID の順序性を確保するには

話題の ULID についてちょっと調べてみました。ミリ秒単位でユニークでソート可能ということなので、その辺について試してみました。

スリープ無し

まずは生成時刻間に(あまり)間があかない場合。

#!/usr/bin/env python

import ulid

def _main():
    n_compare = 1000000
    values = []
    for i in range(n_compare+1):
        values.append(ulid.new())

    eq, gt, lt = 0, 0, 0

    for i in range(n_compare):
        if values[i] == values[i+1]:
            eq += 1
        elif values[i] < values[i+1]:
            gt += 1
        else:
            lt += 1

    print('eq', eq/n_compare)
    print('gt', gt/n_compare)
    print('lt', lt/n_compare)

if __name__ == '__main__':
    _main()

こんな感じで100万回比較できるように 1,000,001 個の ULID を生成してみました。結果は以下の通り。

eq 0.0
gt 0.501897
lt 0.498103

実行するたびにちょっとずつ差はありますが、同じ ULID を生成してしまうことはありませんでしたが、後で作った方が辞書順で後になる確率は半分くらいです。

1ミリ秒スリープ

ここで、 ULID を生成するタイミングにスリープを入れてみます。

#!/usr/bin/env python

import time
import ulid

def _main():
    n_compare = 1000000
    values = []
    for i in range(n_compare+1):
        values.append(ulid.new())
        time.sleep(0.001)

    eq, gt, lt = 0, 0, 0

    for i in range(n_compare):
        if values[i] == values[i+1]:
            eq += 1
        elif values[i] < values[i+1]:
            gt += 1
        else:
            lt += 1

    print('eq', eq/n_compare)
    print('gt', gt/n_compare)
    print('lt', lt/n_compare)

if __name__ == '__main__':
    _main()

こんな感じで1ミリ秒のスリープを入れてみました。当然ながら1,000秒近く時間がかかってしまいます。結果は以下の通り。

eq 0.0
gt 1.0
lt 0.0

スリープを入れなくても同値は無かったので、こちらも当然同値は無し。確実に辞書順になっています。

0.1ミリ秒スリープ

続いて、スリープ時間を0.1ミリ秒にしてみます。

eq 0.0
gt 0.571606
lt 0.428394

微妙ですね。

0.5ミリ秒スリープ

eq 0.0
gt 0.873181
lt 0.126819

概ね辞書順ではあるけどやはり微妙。

まとめ

ULID の生成にスリープ無し、0.1ミリ秒スリープ有り、0.5ミリ秒スリープ有り、1ミリ秒スリープ有りで試してみました。生成の間に1ミリ秒開けると辞書順になりそうですが、それより短い場合だと必ずしも辞書順とは言えなくなりそうです。


この記事が気に入ったらサポートをしてみませんか?