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 ]
バイト列を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