一意な識別子の生成でUUID/ULID/CUID/Nano IDなど検討してみた

最近、一意な識別子について検討することがあったのでその検討メモ。

一意な識別子とは

つまり、重複しない、ユニークな識別子(Identifier, 以下id)のこと。ここではRDBのテーブルにおける主キーとして使うことを想定かつ前提としている。したがって、主キーの要件であるユニーク性を持ったidをどうやって生成していくか。

そんなのDBの連番でいいじゃんて話もあるがここではその話はせず、あくまでも一意な識別子をどう生成するかの話に絞る。

選択肢

一番有名だと思われるUUIDを筆頭にいくつかの選択肢がある。

  • UUID
  • ULID
  • CUID
  • Nano ID

他にもTwitter発のSnowflakeとか今はDeprecatedになってるshortidなどがあるが、キリがないのでここでは上記の4種類だけで簡単に比較した。また、実際にはUUIDはバージョンによってSpecが異なるがここではバージョン4を前提として話をすすめる。また、本来はパフォーマンス比較もしたほうがいいが今回はやれていない。

UUIDの例は 712a865f-79e1-4976-91f1-5150b0e1b9c0 といった形式。UUIDはバージョン4だとランダムに生成されるので耐衝突性が高い、つまりユニーク性は高いとされている。一方で連続性が低く時系列的なソートが効かないなどある。

import { v4 as uuidv4 } from 'uuid';
console.log(uuidv4());

それを解決したといえるものがULIDだ。ULIDはユニーク性を維持しつつミリ秒単位でソートが可能というもので、サンプルとしては01G9BRJVBB3GTYDRT01FBPY24Vという感じだ。ランダムに生成される部分がUUIDは122ビットに対してULIDは80ビットなのでランダム部分のユニーク性はUUIDよりは劣るがタイムスタンプ部分との組み合わせで実質的に問題なさそう。

import { ulid } from 'ulid';
console.log(ulid());

次のCUIDはUUIDの問題を解決しつつULIDよりもいいとされているもの。水平方向のスケーリングとバイナリサーチにおける検索性能の高さを目的としているそうで、cl6a7y23f00006ipga93g41x0といった形式。UILDとの比較はこちらにあるが端的に言うと、

  • CUIDはCSRNGに依存していない
  • これまで衝突が報告されたことはないし、多くのパッケージやリポジトリで使われている

だそうだ。多くのパッケージで使われているとはいえUUIDには遠く及ばない。だが個人的には十分な数ではないかと思う。

import cuid = require('cuid');
console.log(cuid());

Nano IDは端的に言うとUUIDをより小さく・高速にしたものと言える。実際の例はFijSqjkogl2JzJ16P1trhといった形式で確かに文字数としても少ないことがわかる。

import { nanoid } from 'nanoid';
console.log(nanoid());

最後に、UUIDはRFC4122 (https://datatracker.ietf.org/doc/html/rfc4122)として標準化されているのは人によってはポイント高いかも?

ダウンロード数

npmのダウンロード数も比較してみた。

うん、比較が意味をなさないくらいに圧倒的にUUID。そしてNano ID。ULIDとCUIDについてはこの2強の前には大した違いがないレベル。

というわけでULIDとCUIDだけで比較。

CUIDのほうが倍くらい使われてるっぽい。これはちょっと意外。Googleで検索すると体感ではULIDのページのほうがヒットした印象だったので。

注意

前述の通り、上記の比較はあくまでも簡単な比較であって生成にかかるパフォーマンスの比較やベンチマークはしていない。これはどこかで実際に自分でやってみたいところ。

まとめ

結論として何を使うかって話だけど基本はUUID、個人的にはUUIDの長ったらしいフォーマットが好みではないのでNano IDでいいかなと思っているがこれは利用するアプリケーションなどにもよりそう。

また、時系列によるソートが必要ならULIDかCUIDだがここは好みだけど実績の多そうなCUIDか。

なお、自分が使っているPrismaではUUID (v4のみ)とCUIDしかサポートされていないこの場合、時系列性のないUUIDのv4だといろんな方面で議論されているようにMySQLではインサート時のパフォーマンスが悪い。なのでPrismaとの組み合わせで使う場合は基本的にCUIDかなと考えている。自分の場合、現在使っているRDBPostgreSQLなのであまり関係ないけど。

©Keisuke Nishitani, 2023   プライバシーポリシー