8
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Golangでソート可能で重複しないIDを生成する。(ULID)

DBのIDの決め方について調べた結果、ULIDを使うのが良さそうなので調べてみました。

※ 筆者はWeb開発初心者です。間違い等ありましたらご指摘おねがいします。

ULIDとは

Universally Unique Lexicographically Sortable Identifierの略のようです。

こちらの記事を読んで頂くのが理解するのに手っ取り早いと思います。

ソート可能なUUID互換のulidが便利そう

私が読んで理解したところ、

  • UUIDという、重複しないIDを生成する方法がある。
  • UUIDは順序性を求めることができないため、ソートができない。
  • 一方、ULIDはタイムスタンプ部があるので時系列でソート可能。
  • 規約的に26文字のstring型で使用する。 (UUIDは36文字)
  • ULIDは従来のUUIDに比べてぶつかる可能性は上がるが、気にする確率ではない。

といった特徴があるようです。

Goのulidパッケージ

github.com/oklog/ulidのパッケージを使用します。

インストール

インストール
$ go get github.com/oklog/ulid

でインストールします。

READMEにはgo get github.com/oklog/ulid/v2でインストールできるとありますが、
実行してもcannot find packageとエラーが出ます。

私の環境だけでしょうか。情報をいただけると助かります。

使い方

ULIDを生成するには、引数にタイムスタンプ部timeと、ランダム部を作るためのentropyが必要です。

実際にコードを見るほうが速いと思うので実行してみます。

サンプルコード

main.go
package main

import (
    "fmt"
    "math/rand"
    "time"

    "github.com/oklog/ulid"
)

func main() {
    t := time.Now()
    entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0)
    id := ulid.MustNew(ulid.Timestamp(t), entropy)

    //結果を出力
    fmt.Printf("Type: %T, Value: %v \n", id, id)
    fmt.Printf("Type: %T, Value: %v \n", id.String(), id.String())
    fmt.Printf("Type: %T, Value: %v \n", id.Time(), id.Time())
    fmt.Printf("Type: %T, Value: %v \n", ulid.Time(id.Time()), ulid.Time(id.Time()))
}

実行結果

実行結果
$ go run main.go
Type: ulid.ULID, Value: 01DQ4H6WA0ZPX4V3GRY7TJ0J70
Type: string, Value: 01DQ4H6WA0ZPX4V3GRY7TJ0J70
Type: uint64, Value: 1571036557632
Type: time.Time, Value: 2019-10-14 16:02:37.632 +0900 JST

使い方の流れ

次のように使います。

  1. 使用する時間を定義

  2. 疑似乱数からMonotonicで単調増加するエントロピーを生成。
    引数の0については後述

  3. MustNewでIDを生成
    同パッケージにNewがありますが、こちらはエラーハンドリングをユーザー側で行うときに使用します。

※ (興味のある方だけ) Monotonicの引数の0について

  • 同じタイムスタンプでIDを生成した際、競合を避けるために1~引数の数をランダムでインクリメントするようです。
  • 0を引数とするとmath.MaxUint32が設定されます。
  • 引数の値が小さいと次のULIDを推測しやすくなります。
  • 詳しくはgodocを参照してください。

よくわからなければ引数を0としておけば問題ないはずです。

注意点

READMEに

Please note that rand.Rand from the math package is not safe for concurrent use.
Instantiate one per long living go-routine or use a sync.Pool if you want to avoid the potential contention of a locked rand.Source as its been frequently observed in the package level functions.

とある通り、mathパッケージのrand.Randは並行使用すると一意性が保たれない恐れがあります。

goroutineごとにインスタンス化するか、sync.Poolを使用すると良いようです。

まとめ

  • 生成されたidのデータ型はULID型
  • DBに使用するときはstringに変換
  • UUIDとは異なり、時間を表示することも可能

これでIDを生成できるので、あとはDBに入れるなりするといいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
8
Help us understand the problem. What are the problem?