Goで色々なIDを生成してみた(UUIDv1, UUIDv4, XID...)
はじめに
Webアプリに限らず、リソース(ユーザーなど)を区別する必要があるアプリケーションにおいて、IDの生成は避けて通れません。
UUIDを目にする機会も多いですが、他のIDについても知っておきたいと思い色々調べてみました。
それぞれのIDの特徴やフォーマット、連続で生成した場合などの違いを頭に入れておくことで、少し楽ができるかもしれません。
今回紹介するID
ID(アイディー)とは、英語の「identification」の略で、個人を一意に識別する情報のことです。
自分で桁数を決めてランダムな値を生成すれば定義上は無限に種類ができますが、今回は代表的なものを紹介します。
※1からインクリメントするものは分かりきっているため紹介しません。
- UUID(v1)
- UUID(v4)
- Snowflake
- ULID
- xid
前提
以下のバージョンにて確認しています。
go version go1.22.1 darwin/arm64
※サンプルコードは連番など確認するため、全て5つずつ出力しています。
UUID(v1)
UUIDについて
UUIDはxxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx
の形式で、8-4-4-4-12といった合計32桁のハイフンで区切られた5つのグループによって構成されています。
M
にはそのUUIDのバージョン番号が入り、執筆時点でv1~v5までの種類があります。
その中でもよく使われているのがv1とv4です。
v1の概要
v1は生成時のタイムスタンプとMACアドレスによって生成されます。
同じ時間に同じデバイスで生成されなければIDが重複することはありません。
v1を生成
Goでv1のUUIDを作成してみます。
パッケージはgoogle/uuidを使用します。
出力結果で、中央のグループは11ee
となっており、先頭が1であることからv1のUUIDが生成できたことが分かります。
また、最後のグループは同じ端末で生成したため同じ値となり、最初のグループで違いが出ています。
ただし....パッと見は全然区別できないので、ミスが起こりやすそうです。
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
for range 5 {
uuidV1, err := uuid.NewUUID()
if err != nil {
panic(err)
}
fmt.Printf("Generated UUID v1: %s\n", uuidV1)
}
}
// Generated UUID v1: 3b9fcdde-de22-11ee-890a-1a1f8839ffc2
// Generated UUID v1: 3b9fd6b2-de22-11ee-890a-1a1f8839ffc2
// Generated UUID v1: 3b9fd6d0-de22-11ee-890a-1a1f8839ffc2
// Generated UUID v1: 3b9fd6ee-de22-11ee-890a-1a1f8839ffc2
// Generated UUID v1: 3b9fd702-de22-11ee-890a-1a1f8839ffc2
v1で使用する関数はNewUUID
です。後述するv4
の生成に用いる関数と名前を間違えないように注意が必要です。
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
UUID(v4)
次はv4のUUIDを生成します。
v4の概要
v4は完全にランダムな文字列です。
途中のバージョン番号4
を除けば、あとはa-z,0-9がランダムに配置されています。
v4を生成
NewRandom()
という関数でv4のUUIDが生成できます。
完全にランダムに作成されているため、人間でも違いが分かります。
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
for range 5 {
uuidV1, err := uuid.NewRandom()
if err != nil {
panic(err)
}
fmt.Printf("Generated UUID v4: %s\n", uuidV1)
}
}
// Generated UUID v4: 10915ff7-f50f-4833-bb72-8a93c0bca757
// Generated UUID v4: a1c3c433-d244-404b-99b1-1f465f693373
// Generated UUID v4: d5e26123-99a0-40b4-af96-c62ea2b3b9d2
// Generated UUID v4: d9f49fef-f4f3-4443-a89c-c49d831e2635
// Generated UUID v4: 315df563-7be4-4327-baa2-c0c2a6179d1f
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// Uses the randomness pool if it was enabled with EnableRandPool.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 10−11),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
}
Snowflake
概要
SnowflakeとはTwitter,Discordで使用されているIDで、生成時間(とマシンID)によって一意に決定されるため、IDを作成日時順にソートすることが可能なIDです。
以下の記事が参考になりました。
生成
パッケージはbwmarrin/snowflakeを使って生成しました。
package main
import (
"fmt"
"github.com/bwmarrin/snowflake"
)
func main() {
// Snowflakeノードの作成
node, err := snowflake.NewNode(1)
if err != nil {
panic(err)
}
// Snowflake IDの生成
for range 5 {
id := node.Generate()
fmt.Println("Generated Snowflake ID:", id)
}
}
// Generated Snowflake ID: 1766481472228691968
// Generated Snowflake ID: 1766481472228691969
// Generated Snowflake ID: 1766481472228691970
// Generated Snowflake ID: 1766481472228691971
// Generated Snowflake ID: 1766481472228691972
連番で生成されているため、ソートできることも確認できました。
ULID
概要
「生成順でソートできるUUID」のようなものです。
フォーマットは違いますがソート可能なユニークIDを生成することができます。
形式はXXXXXXXXXXXXXXXXXXXXXXXXXX
で、10桁のタイムスタンプ部分,16桁のランダム部分で構成され合計26桁の文字列です。
※現在はProposalですが、UUIDのv6
,v7
,v8
でもソート可能にする方向で進んでいるようです。
生成
パッケージはoklog/ulidを使用します。
実行結果はstring
やtime.Time
でも出力することができます。もう少し使い方を知りたい方は、以下の記事を参考にしてみてください。
package main
import (
"fmt"
"math/rand"
"time"
"github.com/oklog/ulid"
)
func main() {
for range 5 {
t := time.Now()
entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0)
id := ulid.MustNew(ulid.Timestamp(t), entropy)
fmt.Printf("Generated ULID: %s\n", id)
}
}
// Generated ULID: 01HRHXD9CEJ9707Z70T9SPK3D0
// Generated ULID: 01HRHXD9CEZTKDDBEQ14T9BNJF
// Generated ULID: 01HRHXD9CEBSYE1CWNGXZSMV5X
// Generated ULID: 01HRHXD9CE83PB0QFC16S7X9W1
// Generated ULID: 01HRHXD9CEKYE9VDAMYCNX97WX
xid
概要
xidは時間でソート可能かつ、url-safeな文字列が生成されるためエンコードせずにそのまま使えるという特徴があります。またUUIDよりサイズも小さいというメリットもあります。
以下はrs/xidに掲載されている各種IDの比較テーブルです
生成
パッケージは最もメジャーなrs/xidを使用します。
package main
import (
"github.com/rs/xid"
)
func main() {
for range 5 {
guid := xid.New()
println("Generated xid:", guid.String())
}
}
// Generated xid: cnm873e5fmasonujg540
// Generated xid: cnm873e5fmasonujg54g
// Generated xid: cnm873e5fmasonujg550
// Generated xid: cnm873e5fmasonujg55g
// Generated xid: cnm873e5fmasonujg560
結果を見てもソート可能であることが分かります。
さいごに:UUIDとGUIDの違い
GUIDはMicrosoftによるUUIDの標準の実装です。
GUIDは全て大文字である必要がある一方、UUIDは入力時に大文字と小文字を区別せず、出力時は小文字という仕様があります。
GUIDの例
51D39A3F-0687-4183-A429-FC553B552ACB
UUIDの例
de56d4c1-9570-4d43-e686-a4cc156c2f8e
異常、いろんなIDをGoで生成してみました。
他にも便利なIDやもっと効率的な方法などありましたら教えていただけると嬉しいです!
Discussion