ID生成(AUTO_INCREMENT, UUID等)#

概要#

IDの生成は

  • RDBMSでの主キー設計

  • S3バケットでのファイル名設計

  • WEBサイトでのURL設計

などで必要になる。

主に「ソートが可能か」「分散システムで扱いやすいか」「推測されやすいか」など様々な観点で優れたアルゴリズムが考案されてきた。
RFCで標準化されているものとしてはUUIDv7(2024年)が最新。

(参考:主キーはもう「UUIDv7」一択なのか? 〜 ID技術の歴史的変遷と現時点の最適解 〜

アルゴリズム

時系列ソート

分散システム

耐推測性

JSの相性

AUTO_INCREMENT

❌(連番で推測容易)

UUIDv4

✅(ランダム)

UUIDv7

⚠️(時刻が露出)

ULID

⚠️(時刻が露出)

Snowflake

❌(構造的に推測可能)

⚠️

主要なID生成方法#

AUTO_INCREMENT(連番)#

MySQLの AUTO_INCREMENT や PostgreSQLの SERIAL などの連番ID

値の例#

1, 2, 3, 4, ...

アルゴリズム概要#

  • DBが内部カウンタを持つ

  • INSERTごとに +1

  • トランザクションとロックにより一意性を保証

next_id = last_id + 1

長所#

  • シンプル・高速、B-Treeと相性がいい

  • ストレージ効率が良い(INT/BIGINT) → 64bit程度

  • ソート不要(順序=時間)

短所#

  • 分散環境と相性が悪い(単一ノード前提):分割(シャーディング)してても、「next_idは?」の問い合わせは中央のRDBMSに問い合わせる必要

  • IDから件数・増加速度が推測可能(情報漏洩)

UUIDv4#

UUID(Universally Unique Identifier)は2005年にRFC 4122で提案されたID生成方法。 RFC 4122でUUIDv1~v5が提案された。

値の例#

8桁-4桁-4桁-4桁-12桁のフォーマットとなっている

4e5700a4-8775-4b81-b064-40dc1327cdf5

UUIDv1では1~3番めの項がタイムスタンプで最後の12桁がMACアドレスといった意味合いで分けられていた

UUIDv4では 乱数[8桁]-乱数[4桁]-先頭1桁がバージョン(4=v4, 7=v7) [4桁]-先頭1桁がvariant[4桁]-乱数[12桁] と、ほぼ乱数で埋められている

xxxxxxxx-xxxx-4xxx-axxx-xxxxxxxxxxxx
              ↑    ↑
           version variant

アルゴリズム概要#

UUIDv4は「ランダムな122ビット値で生成される一意識別子」

  • 122bit:乱数

  • 6bit:固定(version/variant)

    • versionは4に固定

    • variantは8~b

現在はv4やv7が主流。なお同じRFCに記載されたv1~v5は次のようになっている

  • UUIDv1:時間+MACアドレスを埋め込む。マシンのMACアドレスを直接含むためセキュリティリスク(特定可能性)が高く、加えて時間のバイト並びの都合でDBでのソートにも不向き

  • UUIDv2:POSIXのUIDなどを埋め込んだ特殊仕様

  • UUIDv3 / v5:任意の入力値(文字列など)をハッシュ化(v3はMD5、v5はSHA-1)して生成する「同一入力からは常に同じIDが生成される(決定論的)」特殊な用途のUUID

長所#

  • 分散環境と相性がいい

    • 122bitのとりうる値の数は \(2^{122} \approx 5.3 \times 10^{36}\) (5.3澗)と非常に大きく、衝突確率が極めて低い

  • オフライン生成可能(DB不要)

  • 連番と違い、次の値が推測困難(セキュリティ的に良い)

短所#

  • インデックス性能が悪い(ランダム挿入)

  • サイズが大きい(16byte)

  • 可読性が低い

なお、Pythonだと標準パッケージのuuidから簡単に利用できる

from uuid import uuid4
uuid4()
UUID('2135b975-4b50-4816-87fe-bf711a56a145')

Snowflake ID(Twitter Snowflake)#

Twitter(現:X)が設計した、分散システムでソート可能な一意なIDを生成するアルゴリズム

値の例#

64bit整数で表現される

1463248576239845376

アルゴリズム概要#

64bit整数をビット分割

| 1bit | 41bit        | 10bit        | 12bit        |
| 符号 | タイムスタンプ | ノードID     | シーケンス番号 |
  • 41bit: timestamp

    • ミリ秒単位の時間 → これによりソート可能

    • 約69年分使える

  • 10bit: node ID / マシンID

    • 最大1024ノード

  • 12bit: sequence

    • 同一ミリ秒内でのカウンタ

    • 1ミリ秒あたり最大4096件生成可能

timestamp, node ID, sequence が完全に一致しない限りは衝突しない

長所#

  • 分散環境で衝突しない(ノードIDのため)

  • 整数なので高速・コンパクト

  • 時系列ソート可能 → インデックス効率が良い(特にB-tree)

短所#

  • ノードID管理が必要(運用コスト)

    • k8sの各インスタンスに対して0~1023の被らないノードIDを割り振り続ける、という中央集権的な管理システムが必要

  • クロック逆行問題(時計ずれ)

  • 実装の複雑さ

  • JavaScriptと相性が悪い

    • JSの整数は内部的に53bitしかないため64bit整数は扱えない(文字列にすれば扱える)

ULID#

2016年に登場したULID(Universally Unique Lexicographically Sortable Identifier)は、「一意性を保ちつつ、文字列のまま時系列順にソートできるID」

値の例#

26文字の文字列で表される。

01HZY3J8K9X5Y7Z6A1B2C3D4E5

アルゴリズム概要#

128bitの値を Crockford Base32エンコード したものになっている。128bitの内訳は次の通り

  • 48bit:timestamp(ms)

  • 80bit:乱数

[ timestamp | randomness ]
Crockford Base32 Encodingとは

バイト列を5bit(\(2^5=32\)通りの値)ごとに分割し、32文字の対応する文字列

0123456789ABCDEFGHJKMNPQRSTVWXYZ

にマッピングするエンコーディング。

RFCで標準化されている通常のBase32 Encodingは

ABCDEFGHIJKLMNOPQRSTUVWXYZ234567

を使うので辞書順にできるように少し改変されている

長所#

  • 文字列としてソート可能(辞書順=時間順)

  • URLセーフ

  • UUIDより短くて読みやすい

短所#

  • UUIDほどの標準化はない

  • 同一ms内で衝突対策が実装依存

UUIDv7#

2024年にRFC 9562で標準化されたUUIDの新しいバージョン。
RFC 9562ではv6(v1の改良版), v7, v8(独自フォーマットでカスタムするための自由枠)が記載されている。

値の例#

01890f3e-6b6c-7cc2-b1f2-8f9c2d4a9e12

UUID = 4hexOctet “-” 2hexOctet “-” 2hexOctet “-” 2hexOctet “-” 6hexOctet

アルゴリズム概要#

128ビットのうち 先頭48ビットをUnixタイムスタンプ(ミリ秒単位、西暦10889年まで有効)とし、残りの約74ビットを乱数とする

  • 48bit:Unix時間(ミリ秒)

  • 74bit:乱数

[ timestamp | random ]

長所#

  • 分散と相性がいい

  • 時系列ソート可能

  • インデックス局所性が改善

短所#

  • 完全な順序ではない(同一ms内はランダム)

  • 実装がまだ統一されていないケースあり

from uuid import uuid4
uuid4()
UUID('ab68390b-714d-4df5-ab2b-7be415b8d07a')

まとめ#

アルゴリズム

時系列ソート

分散システム

耐推測性

JS安全性

AUTO_INCREMENT

❌(連番で推測容易)

UUIDv4

✅(ランダム)

UUIDv7

⚠️(時刻が露出)

ULID

⚠️(時刻が露出)

Snowflake

❌(構造的に推測可能)

⚠️(64bit問題)

使い分け#

  • 単一DB → AUTO_INCREMENT

  • 分散 or 2026年現在の無難な選択肢 → UUIDv7