Generierung von IDs
1) Warum auf Identifikatoren achten
Identifier (ID) - der grundlegende Schlüssel der Entität: DB-Zeilen, Nachrichten, Datei, Bestellung. Von seinen Eigenschaften hängen ab:- Einzigartigkeit und Maßstab (Kollisionen, horizontales Wachstum).
- Ordnung und Sortierung (Zeitkorrelation, Replikation, Dedup).
- Speicherleistung (Indizes, Hot Pages, Schlüsselgröße).
- Sicherheit (Unvorhersehbarkeit, Lecks, Erraten).
- Usability/Integration (kurz, URL-safe, nicht Groß-/Kleinschreibung).
Die Wahl der ID ist ein Kompromiss zwischen Entropie, Ordnungsmäßigkeit, Länge, Erzeugungsrate und Ausbeutung.
2) Wichtige Anforderungen und Begriffe
Einzigartigkeit: Die Wahrscheinlichkeit einer Kollision muss unter dem akzeptablen Risiko liegen.
Entropie: „wie viel Zufall“ enthält die ID (Bit).
Sortierbarkeit (time-sortable/k-sortable): lexikographische Sortierung ≈ Sortierung nach Zeit.
Monotonie: nicht abnehmende Sequenz innerhalb eines Knotens/Flusses.
Schreiblokalität: Wie stark konzentriert sich die neue Einfügung im „Schwanz“ des Index (Gefahr heißer Seiten).
Vorhersagbarkeit: ob benachbarte IDs erraten werden können (wichtig für Sicherheit/API).
Darstellung: binär/Zeichenkette, Base16/32/36/58/64, Bindestriche, Register.
3) Die wichtigsten ID-Familien
3. 1 UUID
v4 (random): 122 Bits Entropie. Ungeordnet, gut für Sicherheit und Einfachheit. Der Nachteil: Die Indizes „chaotisch“ durch die zufällige Verteilung - was jedoch die Lasten gleichmäßig verteilt und die „heißen Seiten“ entfernt.
v1 (Zeit + MAC): ordnen, aber trägt IAS/Zeit (Privatsphäre); oft vermieden werden.
v7 (time-ordered): Millisekundenzeit + Zufallsteil. Design für lexikographische Sortierung nach Zeit und gute Kompression in der DB. Kompromiss: Ein „heißer Schwanz“ des Index erscheint; wird mit Sharding/Präfixen/Inkrement behandelt.
Tipps
Für externe APIs und lasche Ordnungsanforderungen - v4.
Für Ereignis/Log-DBs und „sortierbare“ Schlüssel - v7.
3. 2 ULID (Crockford Base32)
128 Bit: 48 Bit Zeit (ms) + 80 Bit Zufall. Lexikographisch sortiert nach Zeit, menschenfreundlich (ohne'I, L, O, U'), URL-sicher. Es gibt eine monotone Variation (bei gleichem Zeitstempel erhöht sich der Zufallsanteil).
Vorteile: Lesbarkeit, Bestellbarkeit, Übertragbarkeit.
Nachteile: Bei einer sehr hohen Häufigkeit von Einsätzen zu einem Zeitpunkt - „heißer Schwanz“.
3. 3 KSUID
160 Bit: 32 Bit Zeit (s) relativ zur Epoche + 128 Bit Zufall. Größerer Zeitbereich und stabile Sortierbarkeit, Strings kürzer als ULID? (nein - länger, aber mit eigener Kodierung), gut für verteilte Protokolle und Objekte.
3. 4 Snowflake-like (k-sortable flake IDs)
Klassisches Schema (konfigurierbar):
[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]
Eigenschaften: monotones Wachstum auf dem Knoten, quasi globale Einzigartigkeit, kurze (64 Bit) binäre Darstellung.
Risiken: Abhängigkeit von der Uhr (Zeitverschiebung/-regression), Erschöpfung der Sequenz in einem Tick, Koordination der Region/Worker-Bits.
Behandelt mit: Schutz vor „clock back“, Sequenzreserve, Zeitdetektor, PTP/NTP-Disziplin.
3. 5 DB-Sequenzen (SEQUENZ/IDENTITÄT)
Einfachste monotone Erzeugung in einem DBMS/Chard.
Vorteile: kurz, schnell, praktisch für lokale Tabellen.
Nachteile: schwierig global in einem verteilten Cluster; vorhersehbar (unsicher als öffentlicher Schlüssel), erzeugt einen heißen Schwanz des Index.
3. 6 Inhaltsadressierte IDs (Hash Content)
SHA-256/Blake3 vom Inhalt → stabile ID, Deduplizierung, Integritätsprüfung, Caching.
Vorteile: Determinismus, Schutz vor Substitution.
Nachteile: teure Erzeugung (CPU), praktische Nullkollisionen, keine zeitliche Sortierung, Länge.
4) Kollisionen und das „Geburtstagsparadox“ (intuitiv)
Die Kollisionswahrscheinlichkeit für eine zufällige ID der Größe'b 'Bit bei' n 'Generationen ist näherungsweise:
p ≈ 1 - exp (-n (n-1 )/2/2 ^ b) ≈ n ^ 2/2 ^ (b + 1) (for small p)
Beispiele:
- UUIDv4 (122 Bit) bei n = 10 ^ 12 (Trillion) → p ~ 1e-14 (vernachlässigbar).
- Der 64-Bit-Rand → bei n = 10 ^ 9 bereits p ~ 0. 027 (auffälliges Risiko).
- Fazit: 64-Bit-Random ist oft klein für riesige Systeme; Verwenden Sie 96/128 Bit.
5) Indizes, heiße Seiten und Lagerung
Zufällige Schlüssel (v4) verteilen die Einfügungen gleichmäßig auf dem Indexbaum → es gibt keinen „Schwanz“, aber die Cache-Lokalität ist schlechter.
Zeitlich sortierbare (v7/ULID/Snowflake) werden „in den Schwanz“ eingefügt → bessere Lokalität und Kompression, aber das Risiko von heißen Seiten unter hoher paralleler Aufzeichnung.
- Präfixe/Sharding von tenant/region (1-2 Bytes vor der Zeit hinzufügen);
- interleaving: Teil des Zufalls in den höheren Bits;
- Batch-Einsätze, Fillfactor im B-Baum, Auto-Übergang zu BRIN/Clustering für große Logs.
- 'UUID (16B)' vs' BIGINT (8B) '/' INT8 'spart Speicher/Cache; Zeilen erhöhen Base32/58/64 die Größe um 20-60%. Für DB binär speichern, in die Zeile am Rand serialisieren.
6) Sicherheit und Privatsphäre
Verwenden Sie SEQUENCE/INT nicht als öffentliche IDs in der URL/API: Erraten → Auflisten von Ressourcen.
Fügen Sie zufällige, unvorhersehbare IDs (v4/v7/ULID/KSUID) für externe Links hinzu.
Codieren Sie nicht PII in ID. Wenn Sie ein Attribut aktivieren müssen - verschlüsseln/signieren (z. B. JWE/JWS) oder undurchsichtige Token verwenden.
URL-sichere Kodierungen: Base32 Crockford, Base58 (ohne' 0OIl'), Base64url.
7) Multi-Tenant, Präfixe und Routing
Format: „[TENANT _ PREFIX] - [ID]“ oder binär: „tenant _ id | | id“.
Vorteile: Schnellfilter/Parteien nach Mieter, Schutz vor N + 1 Scans.
Nachteile: kann die Dichte der Entropie in den höheren Bits verschlechtern → denken Sie über die Verteilung nach (Präfix-Hash).
Das Hash-Suffix (2-3 Bytes) reduziert Kollisionen und hilft beim Shard-Routing: 'shard = hash (id)% N'.
8) Praktische Empfehlungen für die Auswahl
APIs, öffentliche Links, verteilte Dienste ohne strenge Reihenfolge: UUIDv4, ULID/KSUID.
Protokolle/Ereignisse/Aufträge, bei denen wir oft nach Zeit sortieren: UUIDv7 oder ULID (monoton).
Extrem hohe Bandbreite mit lokaler Monotonie und kurzem Schlüssel: Snowflake-like 64-bit (Zeitdisziplin erforderlich).
Artefakt/Bild-/Blob-Repositories: Content-Addressed (SHA-256) und darüber ein menschenfreundliches kurzes „Schaufenster“ (Hashids/Link).
Lokale Tabellen in einer DB: SEQUENCE/IDENTITY + externer „Wrapper“ für öffentliche Links (Masking).
9) Implementierungen und Beispiele
9. 1 PostgreSQL
Speichern Sie die UUID binär, die Indizes sind 'btree' oder 'hash' nach Bedarf.
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);
Sequentielle Hot Fix: für die Zeit-sorted ID, fügen Sie „Salz“ zu den höheren Bits oder Partituren von tenant:
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 (Atomzähler/Monontonium)
bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20 (worker_id<<10) (seq & 1023)
9. 3 Snowflake-ähnlicher Generator (Pseudocode)
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 in Anwendungen
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())
Node. 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) Kodierungen und Darstellungen
Binär in der DB ('BYTEA', 'UUID') → kompakt und schnell. Am Rand konvertieren in:- Base32 Crockford (ULID): Groß- und Kleinschreibung unempfindlich, ohne visuell ähnliche Zeichen.
- Base58: kurz Base32/64 für menschliche Token, URL-safe.
- Base64url: kurz, aber'- 'und' _ 'in der URL.
Register und Format (Bindestriche/keine) stabilisieren, um Duplikate beim Zeilenvergleich zu vermeiden.
11) Test-Playbooks und Beobachtbarkeit
Kollisionen: Metrik 'id _ collision _ total' (muss 0 sein), alert bei> 0.
Präfix-Verteilung: Histogramm der älteren Bytes - Suche nach Aufkäufen.
Generierungsrate: 'ids _ per _ sec', p99 der Generatorlatenz.
Clock skew (für Snowflake): Offset von Knoten, „clock went back“ -Ereignissen.
Indexschwänze: p95/p99 „INSERT“ Latenz; Anteil der Sperren/Hot Pages.
- Injection „clock drift/back“ → stellen sicher, dass der Generator wartet/schaltet.
- Ein „sequence“ -Überlauf in einer Millisekunde → ein Wartetest auf next_ms.
- Massive Parallelität → Gibt es keine Sperrstürme im Index?
12) Anti-Muster
AUTO_INCREMENT/SEQUENCE als öffentliche ID: erraten, Lecks. Verwenden Sie eine öffentliche undurchsichtige ID über der internen ID.
UUIDv1 (MAC/Zeit) nach außen: Privatsphäre.
64-Bit-Random-ID pro Billionen Datensätze: echtes Kollisionsrisiko.
Globaler „Zentralgenerator“ ohne HA: SPOF und Flaschenhals.
Time-sorted IDs ohne Clock-Back-Schutz: Duplikate/Ordnungsregression.
Das Mischen verschiedener ID-Formate ohne explizite Version/Präfix → Chaos in Debags/Migrationen.
Speichern Sie die ID als Zeichenfolge mit verschiedenen Registern/Formen → versteckte Duplikate.
13) Checkliste Umsetzung
- Ausgewähltes Format (v4/v7/ULID/KSUID/Snowflake/SEQ/hash) für Domänenanforderungen.
- Die Anforderungen an die Reihenfolge (ob Sortierbarkeit notwendig ist) sind definiert.
- Die Kollisionswahrscheinlichkeit (b Bit, n Generationen) wurde abgeschätzt und die Risikoschwelle vorgegeben.
- Codierung entworfen (binär in DB + menschliche lesbare Vitrine).
- Für Time-sorted - Schutz vor Clock Back, Sequence Limits und NTP/PTP Disziplin.
- Für öffentliche IDs - Unvorhersehbarkeit (random/ULID/KSUID), keine PII.
- Shard Routing (hash (id)% N), Multi-Tenant-Präfixe sind durchdacht.
- Beobachtbarkeit: Metriken von Kollisionen, Verteilung, Verzögerungen, Uhr skew.
- Testfälle für Überlaufsequenz/hohe Konkurrenz/Fensterlänge.
- Dokumentation von Format, Version, Epoche, Bitmarkup und Migrationsplan.
14) FAQ
Q: Was ist „Standard“ für Microservices zu wählen?
A: UUIDv7 oder ULID: Geordnete Zeit, viel Entropie, einfache Erzeugung am Rand. Für externe APIs - ULID/UUIDv4 auch ca.
F: Sie benötigen eine kurze und lesbare ID.
A: ULID/KSUID oder Base58-Kodierung 128-Bit zufällige/temporäre ID. Denken Sie an Länge und Kollision.
Q: Ist es möglich, eine „kurze numerische“ ID zu machen, aber sicher?
A: Ja: Speichern Sie die interne SEQ und geben Sie den opaque Token (96-128 Bit Rand) oder Hashids mit Salz + Signatur nach außen.
F: Wie migriere ich von SEQ zu UUIDv7?
A: Geben Sie eine neue Spalte' id _ new'(UUID) ein, doppelwandig, veröffentlichen Sie Referenzen auf die neue ID, schalten Sie dann RC/Fremdschlüssel um und löschen Sie die alte.
Q: Warum sind meine Einsätze mit ULID „heiß“ geworden?
A: Fügen Sie streng steigende Schlüssel in einen Index ein. Teilen Sie in Partitionen/tenant, mischen Sie die höheren Bits, verwenden Sie Batch-Einsätze.
15) Ergebnisse
Eine gute ID ist der richtige Satz von Eigenschaften für eine Aufgabe: genug Entropie, vorhersehbare Sortierung (falls erforderlich), sichere Werbung und gesunde Nutzung von Indizes. Wählen Sie UUIDv4/ULID/UUIDv7/KSUID für Einfachheit und Verteilung, Snowflake für dichte Monotonie und kurze Schlüssel (unter Zeitdisziplin), Sequenzen für lokale Tabellen, Content-Hashes für Artefakte. Legen Sie Beobachtbarkeit und Tests fest - und IDs sind keine Quelle für Überraschungen mehr.