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
が必要です。
実際にコードを見るほうが速いと思うので実行してみます。
サンプルコード
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
使い方の流れ
次のように使います。
-
使用する時間を定義
-
疑似乱数から
Monotonic
で単調増加するエントロピーを生成。
引数の0については後述 -
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に入れるなりするといいと思います。