every Tech Blog

株式会社エブリーのTech Blogです。

SonyflakeでUnique IDentifierを生成しよう

はじめに

この記事は every Tech Blog Advent Calendar 2024 の4日目の記事です。

DelishKitchenやヘルシカのバックエンドやらインフラやらをやっているyoshikenです。

今回は弊社でも利用しているUID生成に便利なSonyflakeについて説明していきます。

UIDとUUIDの違い

まず、UIDとUUIDの違いについて理解をしましょう。

UUID

RFC 9562で標準化されている"普遍的にユニークな識別子"のことです。UUIDは、主にデータベースの主キーや分散システムにおけるオブジェクト識別子として使用され、形式は以下のようになっています。

例: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 (8-4-4-4-12 計32文字16進数というフォーマットです。細かい仕様はRFCを参照してください。)

注: RFC 4122は既に廃止されています。【RFC 9562】新しい UUID の概要紹介 | ymstmsys site

UUIDの目的は、 グローバル規模での一意性の担保 です。

UID

こちらは特に定まったフォーマットなどはなく、特定のスコープ内で一意に識別が可能というのが目的です。

DBのAuto Incrementもそういう意味ではUIDと呼べます。

ソフトウェアの世界に限らず、社員番号や学校のクラスの出席番号もある意味UIDと呼んで差し支えないかと思います。

ここでの注意点はUUIDの目的であった グローバル規模での という点は保証していないということです。

たとえば僕のeveryでの社員番号が200番だったとしても、他社で社員番号が200番の人は別の人を指しています。

DBのAuto Incrementも別テーブルでは衝突をしてしまいます。

そういったグローバル規模での一意性を担保するのであればUUIDを使用すべきです。

とはいえ、例えば学校のクラス40人程度にUUIDで一意性を〜というのはオーバーエンジニアリングになってしまうので、UIDで出席番号を割り振る程度がコストも掛からず可読性もよくなります。

クラスや学年が変わると被ってしまうので、スコープを学内にして学籍番号にするなど、適切にコントロールをしていくことでUIDだけでも問題ない場合があります。

卒業式に「6年2組、出席番号番号f81d4fae-7dec-11d0-a765-00a0c91e6bf6、吉田健太」なんて聞きたくないですね。

Sonyflake

sony/sonyflake: A distributed unique ID generator inspired by Twitter's Snowflake

Sonyflake is a distributed unique ID generator inspired by Twitter's Snowflake.

Sonyflake focuses on lifetime and performance on many host/core environment.

READMEにも書いてあるとおり、Twitter社のSnowflakeをインスパイアした分散型UID生成ライブラリです

構成要素は以下の通りで、合計63ビットで表現されます。

  0                         15               32 (ビット)
  +--------------------------+-----------------------------+
  |          タイムスタンプ (39ビット)                        |
  +--------------------------+-----------------------------+
  |タイムスタンプ(続き)| シーケンス (8ビット)|マシンID (16ビット)|
  +------------------------------------------------------+

実際に出力されるのは以下のような数列が生成されます。

542479593760621806

以下は生成するexampleコードです。

package main

import "github.com/sony/sonyflake"

func main() {
    instance := sonyflake.NewSonyflake(sonyflake.Settings{})
    if instance == nil {
        panic("sonyflake not created")
    }
    id, err := instance.NextID()
    if err != nil {
        panic("ID not created")
    }
    println(id)
}

弊社ではレシピのナンバリングやユーザーID発行に使用されています。

選定理由ですが、随分前なので正確なところが不明ですが、Sonyflakeが現行のUUIDv7と比べても

  • UUIDv7が128bitにたいしてSonyflakeは63bitとサイズが小さく取り回しがよい
  • Sonyflakeは全て数値かつ単調増加に近しい挙動なのでB-treeインデックスを考えると効率が良い
    • UUIDv7もタイムスタンプがあり以前に比べるとパフォーマンスが良くなりましたが、それでもSonyflakに歩があります
  • 生成コストが安い
  • 見た目がわかりやすい(可読性)

というメリットがあります。

そもそも導入当時の2016年前後ではUUIDv7はRFC化されておらず、ULIDも出始めギリギリといったところです。

またSonyflake自体分散システム利用されることが前提のため、オートスケールで複数台のノードが立ち上がっていても問題となりません。

以上のことから当時Sonyflakeを選定するのは妥当性があると思います。

また導入から現在も特に大きな障害は発生しておらず安定して運用できています。

まとめ

Sonyflakeは、UUIDv4に比べて生成コストが低く、インデックスの効率も良いというメリットがあります。しかし、UUIDv7ほどのユニーク性は持っていないため、衝突が許されない要件での使用には適していません。

用法用量を正しく守り使用すれば高いパフォーマンスを発揮することが期待できます。

以上でSonyflakeの紹介を終わります