🐈

Goで色々なIDを生成してみた(UUIDv1, UUIDv4, XID...)

2024/03/10に公開

はじめに

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が生成できたことが分かります。

また、最後のグループは同じ端末で生成したため同じ値となり、最初のグループで違いが出ています。
ただし....パッと見は全然区別できないので、ミスが起こりやすそうです。

main.go
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の生成に用いる関数と名前を間違えないように注意が必要です。

google/uuid/version1.go
// 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が生成できます。
完全にランダムに作成されているため、人間でも違いが分かります。

main.go
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
google/uuid/version4.go
// 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です。

以下の記事が参考になりました。
https://qiita.com/vex12853/items/ed63f32d84ebc790a574#fn-6

生成

パッケージはbwmarrin/snowflakeを使って生成しました。

main.go
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桁の文字列です。

https://qiita.com/kai_kou/items/b4ac2d316920e08ac75a

※現在はProposalですが、UUIDのv6,v7,v8でもソート可能にする方向で進んでいるようです。
https://kakakakakku.hatenablog.com/entry/2022/10/31/082041

生成

パッケージはoklog/ulidを使用します。

実行結果はstringtime.Timeでも出力することができます。もう少し使い方を知りたい方は、以下の記事を参考にしてみてください。

https://zenn.dev/emiksk/articles/e2716c0af75eea

main.go
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を使用します。

main.go
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