IDの生成
1)識別子に注意を払う理由
識別子(ID)-エンティティの基本キー:データベース行、メッセージ、ファイル、順序。その特性は以下に依存します:- 独自性とスケール(衝突、水平成長)。
- 注文と並べ替え(時間の相関、レプリケーション、デッドアップ)。
- ストレージのパフォーマンス(インデックス、ホットページ、キーサイズ)。
- 安全性(予測不能、リーク、推測)。
- ユーザビリティ/統合(短い、URL-safe、大文字と小文字を区別しない)。
IDを選択することは、エントロピー、秩序性、長さ、生成速度、および搾取の間の妥協点です。
2)主な要件と条件
一意性:衝突の確率は許容されるリスクよりも低い必要があります。
エントロピー:「どのくらいのランダム性」にID(ビット)が含まれています。
Time-sortable/k-sortable-Lexicographic ≈タイムベースのソート。
単調:ノード/ストリーム内の非減少配列。
エントリの局所性:インデックスの「尾」に新しいインサートがどれだけ集中しているか(ホットページの危険性)。
予測可能性:近隣のID (セキュリティ/APIに重要)を推測することは可能ですか?
表現:バイナリ/文字列、Base16/32/36/58/64、ハイフン、ケース。
3)主な識別子ファミリー
3.1 UUID
v4(ランダム):エントロピーの122ビット。無秩序で、安全性とシンプルさに優れています。マイナス:ランダム分布による「カオス」指数-しかし、負荷を均等に分散させ「、ホットページ」を削除します。
v1 (time+MAC):整理しますが、MAC/time(プライバシー)を運びます。しばしば避けられました。
v7 (time-ordered):ミリ秒の時間+ランダム部分。時間による語彙ソートとデータベースの良好な圧縮のための設計。妥協:インデックスの「ホットテール」が表示されます。/prefixes/incrementのshardeningによって扱われる。
ヒント
外部APIおよびlax注文要件の場合-v4。
イベント/ログデータベースと「ソートされた」キーの場合-v7。
3.2 ULID(クロックフォードBase32)
128ビット:48ビットの時間(ms)+80ビットのランダム性。語彙的に時間ごとにソートされ、人間に優しい('I、 L、 O、 U'なし)、URL-safe。モノトーンのバリエーションがあります(同じタイムスタンプで、ランダムな部分が増加します)。
長所:可読性、順序性、可搬性。
短所:ある時点での挿入頻度が非常に高い-「ホットテール」。
3.3 KSUID
160ビット:エポック+128ビットのランダム性に対する32ビットの時間(秒)。より大きな時間範囲と安定したソート、ULIDより短い文字列?(もはや、独自のエンコーディングで)、分散ログやオブジェクトに適しています。
3.4 Snowflake-like (k-sortable flake ID)
古典的なスキーマ(カスタム):
[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]
プロパティ:ノード上のモノトーン成長、準大域の一意性、短い(64ビット)バイナリ表現。
リスク:クロック依存性(時間ドリフト/回帰)、1つのティック内のシーケンスの枯渇、領域/作業者ビットの調整。
処理:「クロックバック」、リザーブシーケンス、時間検出器、PTP/NTP規律に対する保護。
3.5 DBシーケンス(SEQUENCE/IDENTITY)
1つのDBMS/shardで最も単純なモノトーン生成。
長所:短く、速く、ローカルテーブルに便利です。
短所:分散クラスタでは世界的に困難です。予測可能(公開鍵として安全でない)、インデックスのホットテールを作成します。
3.6コンテンツアドレスID(ハッシュコンテンツ)
コンテンツSHA-256/Blake3→安定したID、重複排除、整合性チェック、キャッシュ。
長所:決定論、代替に対する保護。
短所:高価な生成(CPU)、衝突は実用的なゼロ、時間のソートなし、長さです。
4)衝突と「誕生日パラドックス」(直感的)
'n'世代のサイズ'b'ビットのランダムなIDの衝突確率はおおよそ次のとおりです:
p ≈ 1 - exp (-n (n-1 )/2/2 ^ b) ≈ n ^ 2/2 ^ (b + 1) (for small p)
例:
- UUIDv4 (122ビット)n=10^12(兆)→p ~ 1e-14(無視できる)。
- 64ビットランダム→n=10^9で既にp ~ 0。027(顕著なリスク)。
- 結論:64ビットのランダムは、多くの場合、巨大なシステムには十分ではありません。96/128ビットを使用します。
5)インデックス、ホットページ、ストレージ
ランダムキー(v4)はインデックスツリー全体にインサートを均等に分散させる→「尾」はないが、キャッシュの局所性は悪くなる。
タイムソート(v7/ULID/Snowflake)は「尾に」→「より良い局所性と圧縮」と挿入されますが、高い並列録音の下でホットページのリスクがあります。
- 接頭辞/テナント/リージョンによるシャーディング(時間の前に1〜2バイトを追加)。
- interleaving:より高いビットのランダム性の一部;
- バッチインサート、Bツリーのフィルファクタ、大きなログのためのBRIN/クラスタリングへの自動遷移。
- 'UUID (16B)'と'BIGINT (8B)'/'INT8'はメモリ/キャッシュを保存します。Base32/58/64行のサイズは20-60%増加します。データベースの場合、バイナリを保存し、エッジ上の文字列にシリアル化します。
6)セキュリティとプライバシー
URL/API: guessable→リソースの列挙では、SEQUENCE/INTをパブリックIDとして使用しないでください。
外部参照にランダムで予測不可能なID (v4/v7/ULID/KSUID)を追加します。
PIIをIDにエンコードしないでください。属性を有効にする場合は、暗号化/記号(JWE/JWSなど)または不透明なトークンを使用します。
URLセーフエンコーディング:Base32 Crockford、 Base58 ('0OI'なし)、Base64url。
7)複数のテナント、接頭辞およびルーティング
フォーマット:'[TENANT_PREFIX]-[ID]'またはバイナリ:'tenant_id | | id'。
長所:クイックフィルター/テナントパーティー、N+1スキャンに対する保護。
短所:高いビットのエントロピー密度を悪化させる可能性がある→分布を考慮する(接頭辞ハッシュ)。
ハッシュサフィックス(2〜3バイト)は衝突を軽減し、シャードルーティングを助ける:'shard=hash (id)% N'。
8)選択のための実用的な推薦
API、公共リンク、厳密な順序のない分散サービス:UUIDv4、 ULID/KSUID。
ログ/イベント/注文、私たちはしばしば時間ごとにソート:UUIDv7またはULID(モノトーン)。
ローカルモノトニーとショートキーを備えた超高帯域幅:Snowflake風の64ビット(時間規律が必要)。
アーティファクト/ビルド/ブロブの保管庫:content-addressable (SHA-256)、そして上に-男性に優しい短い「ショーケース」(Hashids/link)。
1つのデータベース内のローカルテーブル:パブリックリンク(マスキング)のためのSEQUENCE/IDENTITY+external 「wrapper」。
9)実装と例
9.1 PostgreSQL
必要に応じてUUIDバイナリ、インデックス-'btree'または'hash'を保存します。
sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- или uuid_generate_v4()
created_at timestamptz NOT NULL DEFAULT now(),
tenant smallint NOT NULL
);
-- For time-sortable (UUIDv7) store binary (uuid), generation in the application.
-- If you want a cluster by time:
CREATE INDEX ON orders (created_at DESC);
シーケンシャルホットフィックス:タイムソートされたIDのために、テナントによる上位ビットまたはスコアに「塩」を追加します:
sql
CREATE TABLE orders_t1 PARTITION OF orders FOR VALUES IN (1);
CREATE TABLE orders_t2 PARTITION OF orders FOR VALUES IN (2);
9.2 Redis(原子カウンター/モントニア)
bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20 (worker_id<<10) (seq & 1023)
9.3スノーフレーク型ジェネレータ(擬似コード)
pseudo const EPOCH = 1704067200000 # custom epoch (ms)
state: last_ms=0, seq=0, worker=7, region=3
next():
now = epoch_ms()
if now < last_ms: wait_until(last_ms) # защита от clock back if now == last_ms:
seq = (seq + 1) & ((1<<12)-1) # 12 бит if seq == 0: wait_next_ms()
else:
seq = 0 last_ms = now return (now-EPOCH)<<22 region<<17 worker<<12 seq
9.アプリケーションでの4 ULID/UUID
Go(移動)
go
// ULID t:= time. Now(). UTC()
entropy:= ulid. Monotonic(rand. New(rand. NewSource(t. UnixNano())), 0)
id:= ulid. MustNew(ulid. Timestamp(t), entropy)
//UUID v7 (if there is a library)
id:= uuid. Must(uuid. NewV7())
ノード。js: js
js import { ulid } from 'ulid';
import { v4 as uuidv4 } from 'uuid';
const id1 = ulid();
const id2 = uuidv4(); // v4
Python
python import uuid, time id_v4 = uuid. uuid4()
For v7, use a library (for example, uuid6/7 third-party packages)
10)エンコーディングと表現
データベース内のバイナリ('BYTEA'、 'UUID')→コンパクトで高速。エッジで、次のように変換します:- Base32 Crockford (ULID):大文字を区別せず、視覚的に類似した文字はありません。
- Base58:人間が読めるトークンのための簡単なBase32/64、 URL-safe。
- Base64url: shortですが、URLには'-'と'_'があります。
文字列を比較するときに重複を避けるために、ケースとフォーマット(ハイフン/なし)を安定化します。
11) Playbookおよびobservabilityをテストして下さい
衝突:メトリック'id_collision_total' (0でなければなりません)、アラートは>0です。
プレフィックス分布:ハイバイトのヒストグラム-購入を探しています。
生成レート:'ids_per_sec'、 p99ジェネレータのレイテンシ。
クロックスキュー(Snowflake用):オフセットノード、「クロックが戻った」イベント。
インデックステール:p95/p99 'INSERT'レイテンシ;ロック/ホットページの割合。
- 注入「クロックドリフト/バック」→ジェネレータが待機/スイッチングしていることを確認します。
- 'sequence'がミリ秒単位でオーバーフローする→next_ms待ちチェック。
- 質量平行化→インデックスにロックの嵐があるかどうか。
12)アンチパターン
パブリックIDとしてのAUTO_INCREMENT/SEQUENCE:推測、リーク。内部に公開不透明なIDを使用します。
UUIDv1 (MAC/time) out:プライバシー。
1兆エントリあたり64ビットのランダムID:衝突の実際のリスク。
HAのないグローバルな「中央発電機」:SPOFとボトルネック。
クロックバック保護なしのタイムソートされたID:重複/順序の回帰。
ディベート/マイグレーションで明示的なバージョン/プレフィックス→カオスを使用せずに、さまざまなIDフォーマットをミックスします。
別のレジスタ/フォーム→非表示の重複を持つ文字列としてIDを保存します。
13)実装チェックリスト
- ドメイン要件のための選択されたフォーマット(v4/v7/ULID/KSUID/Snowflake/SEQ/hash)。
- 定義された注文要件(sortabilityが必要かどうか)。
- 衝突の確率(bビット、n世代)を推定し、リスク閾値を設定する。
- エンコーディングは設計されています(DB+人間が読めるショーケースのバイナリ)。
- タイムソートの場合-クロックバック保護、シーケンス制限、NTP/PTP規律。
- 公開IDの場合-予測不能(random/ULID/KSUID)、 PIIがない。
- Think out hash (id)% N、マルチテナント接頭辞。
- Observability:衝突、分布、レイテンシー、クロックスキュー指標。
- Sequence/Contention/Window Length Overflowテストケース。
- フォーマット、バージョン、エポック、ビットマップ、および移行計画ドキュメント。
14) FAQ
Q:マイクロサービスのための「デフォルト」を選ぶべき何か?
A: UUIDv7またはULID:時間順序、エントロピーの多く、エッジでの単純な生成。外部APIの場合、ULID/UUIDv4も約です。
Q:短くて人間が読めるIDが必要です。
A: ULID/KSUIDまたはBase58-128-bitランダム/一時的なIDエンコーディング。長さと衝突について覚えておいてください。
Q:「短い数値」のIDを作ることはできますが、安全ですか?
A:はい:内部SEQを保存し、外部は不透明なトークン(ランダム96-128ビット)または塩+署名付きのハシッドを与えます。
Q: SEQからUUIDv7に移行するにはどうすればよいですか?
A:新しいカラム'id_new' (UUID)、 2トラックを入力し、新しいIDへの参照を公開し、DC/外部キーを切り替えて古いものを削除します。
Q: ULIDインサートが「ホット」になったのはなぜですか?
A:厳密に増やすキーを1つのインデックスに挿入します。パーティション/テナント、ミックスハイオーダービット、バッチインサートを使用します。
15)合計
十分なエントロピー、予測可能なソート(必要に応じて)、安全な宣伝とインデックスの健全な搾取。シンプルさとディストリビューションのためのUUIDv4/ULID/UUIDv7/KSUID、密度の高い単調さと短い鍵のためのSnowflake(時間規律のための)、ローカルテーブルのシーケンス、アーティファクトのコンテンツハッシュを選択します。観測可能性とテストを置き、識別子は驚きの源ではなくなります。