Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

UUIDとULIDを徹底解説!一意識別子の仕組みと使い分けを深く掘り下げる

先日、チームで雑談していたときのことです。DevOpsの達人であるBenが「あのシステムはUUIDじゃなくてULIDにしておけばよかった」とつぶやきました。

ベテランエンジニアらしく、私は特に決定的なことを言わず適当に相槌を打ち、その場を離れるやいなやこっそりGoogleで「そもそもULIDとは何なのか」を検索し始めたのでした。

2時間後。虚ろな目つきで調査を終えて戻ってきた私は、一意識別子の世界が想像以上に広大で奥深いものであることを痛感したのです。

ULIDの話に入る前に、まずは基本に立ち返って「UUIDとは何か」をおさらいしましょう。

「普通の」連番IDにはどんな問題があるのか?

データベースを使う多くのWebアプリケーションでは、自動的に増加する数値型のID(オートインクリメント)がデフォルトで採用されています。たとえばRailsでは次のような挙動になります。

p1 = Person.create!
p1.id
# => 1

p2 = Person.create!
p2.id
# => 2

データベースはレコード作成時にインクリメントされるカウンターを内部に保持しているため、このような連番IDを生成できます。

このパターンはデータベース以外でも見られます。手動でIDを割り当てたい場合、Redisなどの外部ストアに独自のカウンターを持たせることもあるでしょう。

連番IDは低トラフィックのユースケースなら簡単に実装できますが、規模が大きくなるにつれてさまざまな問題が顕在化します。

  • ID発行のために各INSERTが順番待ちをする必要があるため、レコードを並行して作成できない
  • 連番IDの取得にはネットワークラウンドトリップが発生する場合があり、パフォーマンスが低下する
  • 連番IDを発行するデータストアのスケールアウトが難しい。複数サーバー間でカウンターが同期ズレしないよう気を配る必要がある
  • カウンターを持つノードが単一障害点(SPOF)になりやすい

さらに、連番IDは情報を漏らしてしまうという側面もあります。

  • 自分のものでないリソースのIDを容易に推測できてしまう
  • ユーザー登録時に自分のIDが20だったら、そのサービスのユーザー数が約20人だと分かってしまう

UUIDはスケールする識別子

UUIDは連番IDとは少し見た目が異なります。128ビットの数値で、通常は32桁の16進数として表現されます。

123e4567-e89b-12d3-a456-426655440000

UUIDはRFC 4122で定義された特定のアルゴリズムによって生成され、連番IDが抱える多くの問題を解決します。

  • ノード間の状態共有や調整なしに、任意の数のノードでUUIDを生成できる
  • 連番IDより推測されにくい(詳細は後述)
  • データセットの規模を外部に漏らさない

ただし落とし穴もあります。独立した2つのノードが偶然同じIDを生成してしまう可能性がゼロではないのです。これは「衝突(コリジョン)」と呼ばれます。もっとも、128ビットの空間における衝突確率は天文学的に低く、実用上ほぼ無視できる水準です。

UUIDにはさまざまなバリエーションがある

RFC 4122では5種類のUUIDアルゴリズムが定義されており、大きく2つのカテゴリに分類できます。

  • 時刻・乱数ベースのアルゴリズム:実行するたびに新しいUUIDが生成されるタイプ。
    • Type 4:ランダムに生成されたID。新規コードではまずこれが最良の選択肢となるでしょう。
    • Type 1:ホストのMACアドレスと現在のタイムスタンプを含むID。推測されすぎるため非推奨(deprecated)です。
    • Type 2:あまり見かけないタイプ。古い形式のRPC向けに作られたもののようです。
  • 名前ベースのアルゴリズム:同じ入力に対しては常に同じUUIDを生成するタイプ。
    • Type 5:SHA-1ハッシュを使ってUUIDを生成。こちらが推奨されています。
    • Type 3:MD5ハッシュを使用。MD5の安全性が低いため非推奨です。

Rubyではuuidtools gemを使ってUUIDを生成できます。謎に包まれたType 2を除くすべてのタイプに対応しています。

# uuidtoolsのREADMEから拝借したコードです :)
require "uuidtools"

# Type 1
UUIDTools::UUID.timestamp_create
# => #<UUID:0x2adfdc UUID:64a5189c-25b3-11da-a97b-00c04fd430c8>

# Type 4
UUIDTools::UUID.random_create
# => #<UUID:0x19013a UUID:984265dc-4200-4f02-ae70-fe4f48964159>

# Type 3
UUIDTools::UUID.md5_create(UUIDTools::UUID_DNS_NAMESPACE, "www.widgets.com")
# => #<UUID:0x287576 UUID:3d813cbb-47fb-32ba-91df-831e1593ac29>

# Type 5
UUIDTools::UUID.sha1_create(UUIDTools::UUID_DNS_NAMESPACE, "www.widgets.com")
# => #<UUID:0x2a0116 UUID:21f7f8de-8051-5b89-8680-0195ef798b6a>

いよいよULIDへ

補足:当初この記事ではULIDの公式仕様へのリンクを掲載し忘れていました。こちらが仕様です。Rubyをはじめ各言語の実装へのリンクもまとめられています。

ULID(Universally Unique Lexicographically Sortable Identifier/辞書順ソート可能な一意識別子)は、一意識別子の新しいアプローチです。最も分かりやすい違いはその見た目にあります。

01ARZ3NDEKTSV4RRFFQ69G5FAV

ULIDはbase32エンコードされた2つの数値で構成されています。前半がUNIXタイムスタンプ、後半が乱数です。文字セットには、紛らわしい文字(I, L, O, U)を排除したCrockfordのBase32が採用されています。仕様で定義された構造は以下の通りです。

01AN4Z07BY      79KA1307SR9X4MV3

|----------|    |----------------|
 Timestamp          Randomness
   48bits             80bits

この構造は実に興味深いものです。思い出してください。UUIDはタイムスタンプか乱数のどちらかに依存していましたが、ULIDはタイムスタンプ乱数の両方を組み合わせています。

その結果、ULIDには次のような特徴があります。

  • 辞書順(アルファベット順)でソートできる
  • タイムスタンプがミリ秒精度で埋め込まれている
  • UUIDより見た目が美しい :)

これにより、以下のような面白い活用が可能になります。

  • 日付でデータベースをパーティショニングしている場合、ULIDに埋め込まれたタイムスタンプを使って適切なパーティションを選択できる
  • ミリ秒精度で十分なら、別途created_atカラムを用意しなくてもULID自体をソートキーとして使える

一方で、注意すべき点も存在します。

  • アプリケーションによっては、タイムスタンプを外部に露出させることが好ましくないケースがある
  • ミリ秒未満の精度が必要な場合は「ULIDでソート」という手法は使えない
  • ネット上の情報によると、一部のULID実装は完全に堅牢とは言い切れないようだ

まとめ

UUIDは現在も、そして今後も標準であり続けるでしょう。長い歴史があり、あらゆるプログラミング言語でライブラリが整備されています。しかし、分散システムがますます普及する現代においては、新しいアプローチにも目を向ける価値があります。新しい一意IDの方式は、RFC 4122が策定された当時には想定されていなかった問題の解決に役立つかもしれません。あなたのシステムに最適な識別子はどれか、ぜひ一度考えてみてください。

  1. ディープウェブとは?仕組みと安全なアクセス方法を徹底解説

    World Wide Web(ワールドワイドウェブ)とは、インターネットを通じてアクセスできるすべてのWebサイトの集合体です。この巨大なサイト群は、「サーフェスウェブ」「ディープウェブ」「ダークウェブ」の3つに分類できます。本記事では、ディープウェブの正体と、その領域に安全にアクセスするための方法を詳しく解説します。 ウェブの3つの種類:サーフェス・ディープ・ダーク サーフェスウェブ(Surface Web) 「見える」表層部分にあたるのが、オープンウェブとも呼ばれるサーフェスウェブです。Google ChromeやMicrosoft Edge、Firefoxなどの一般的なブラウザでアクセ

  2. Google ChromeとMozilla Firefoxの「v100」は何をもたらすのか?

    ソフトウェアアップデートへの正しい理解を大切にしている私たちにとって、ChromeとFirefoxのバージョン100の到来は待ち遠しいものです。「100」という数字は多くの人にとって縁起の良い節目ですが、GoogleとMozillaにとっては一種の課題となる可能性があります。 そもそも何の話? GoogleのChromeとMozillaのFirefoxは、世界で最も人気のある2つのブラウザです。あなたがこの記事を読んでいるのも、おそらくこのどちらかのブラウザでしょう。そして間もなく、両ブラウザのバージョンは「100」の大台に到達します。 Google Chromeは2022年3月29日に、Mo