Generowanie identyfikatorów
1) Dlaczego zwracać uwagę na identyfikatory
Identyfikator (ID) - podstawowy klucz podmiotu: linie bazy danych, wiadomości, plik, zamówienie. Jego właściwości zależą od:- Wyjątkowość i skala (kolizje, wzrost poziomy).
- Porządek i sortowanie (korelacja czasu, replikacja, dedup).
- Wydajność przechowywania (indeksy, gorące strony, rozmiar klucza).
- Bezpieczeństwo (nieprzewidywalność, wycieki, zgadywanie).
- Użyteczność/integracja (krótki, URL-bezpieczny, nie przypadek wrażliwy).
Wybór identyfikatora jest kompromisem pomiędzy entropią, zamawialnością, długością, szybkością generowania i eksploatacją.
2) Kluczowe wymagania i warunki
Wyjątkowość: prawdopodobieństwo zderzenia musi być mniejsze niż dopuszczalne ryzyko.
Entropia: „ile losowości” zawiera identyfikator (bit).
Czas-sortowalny/k-sortowalny-leksykograficzny sortowanie oparte na czasie.
Monotonia: nie malejąca sekwencja w węźle/strumieniu.
Lokalizacja wpisu: ile nowa wkładka jest skoncentrowana w „ogonie” indeksu (niebezpieczeństwo gorących stron).
Przewidywalność: Czy można odgadnąć sąsiednie identyfikatory (ważne dla bezpieczeństwa/API).
Reprezentacja: binarny/ciąg, Base16/32/36/58/64, łączniki, przypadek.
3) Główne rodziny identyfikatorów
3. 1 UUID
v4 (losowo): 122 bity entropii. Nieuporządkowane, dobre dla bezpieczeństwa i prostoty. Minus: indeksy „chaotyczne” ze względu na rozkład losowy - co jednak równomiernie rozprasza obciążenia i usuwa „gorące strony”.
v1 (czas + MAC): zorganizować, ale nosi MAC/czas (prywatność); często unikane.
v7 (time-ordered): milisekund time + random part. design for lexicographic sorting by time and good compression in the database. Kompromis: Pojawia się „gorący ogon” indeksu; traktowane przez ostrzenie/przedrostki/przyrost.
Wskazówki
Dla zewnętrznych interfejsów API i wymagań dotyczących lax - v4.
Dla baz danych zdarzeń/dzienników i klawiszy „sortowanych” - v7.
3. 2 ULID (Crockford Base32)
128 bitów: 48 bitów czasu (ms) + 80 bitów losowości. Leksykograficznie sortowane według czasu, przyjazne dla człowieka (bez 'I, L, O, U'), URL-safe. Istnieje zmienność monotonowa (z tym samym znaczkiem czasu, część losowa wzrasta).
Plusy: czytelność, zamawialność, przenośność.
Minusy: z bardzo dużą częstotliwością wkładek w jednym momencie - „gorący ogon”.
3. 3 KSUID
160 bitów: 32 bity czasu (sec) względem epoki + 128 bitów losowości. Większy zakres czasu i stabilne sortowanie, ciągi krótsze niż ULID? (nie - już, ale z własnym kodowaniem), dobre dla rozproszonych dzienników i obiektów.
3. 4 Płatki śniegu (k-sortowalne identyfikatory płatków)
Klasyczny schemat (niestandardowy):
[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]
Właściwości: monotonowy wzrost na węźle, quasi-globalna wyjątkowość, krótka (64-bitowa) reprezentacja binarna.
Ryzyko: zależność od zegara (dryf czasowy/regresja), wyczerpanie sekwencji w jednym kleszczu, koordynacja bitów regionu/pracownika.
Traktowane: ochrona przed „zegarem z powrotem”, sekwencja rezerwy, wykrywacz czasu, dyscyplina PTP/NTP.
3. 5 sekwencji DB (SEKWENCJA/TOŻSAMOŚĆ)
Najprostsza generacja monotonu w jednym DBMS/shard.
Plusy: krótkie, szybkie, wygodne dla lokalnych stołów.
Minusy: trudne globalnie w rozproszonym klastrze; przewidywalne (niepewne jako klucz publiczny), tworzy gorący ogon indeksu.
3. 6 Identyfikatory adresu treści (zawartość skrótu)
Zawartość SHA-256/Blake3 → stabilny identyfikator, deduplikacja, sprawdzanie integralności, buforowanie.
Plusy: determinizm, ochrona przed substytucją.
Minusy: drogie pokolenie (CPU), kolizje to praktyczne zera, bez sortowania czasu, długość.
4) Kolizje i „paradoks urodzinowy” (intuicyjny)
Prawdopodobieństwo kolizji przypadkowego ID wielkości 'b' bitów w generacjach 'n' wynosi w przybliżeniu:
p ≈ 1 - exp (-n (n-1 )/2/2 ^ b) ≈ n ^ 2/2 ^ (b + 1) (for small p)
Przykłady:
- UUIDv4 (122 bity) przy n = 10 ^ 12 (bilion) → p ~ 1e-14 (nieistotne).
- 64-bitowy losowy → z n = 10 ^ 9 już p ~ 0. 027 (znaczące ryzyko).
- Wniosek: 64-bitowa losowa jest często niewystarczająca dla ogromnych systemów; Użyj 96/128 bitów.
5) Indeksy, gorące strony i przechowywanie
Losowe klucze (v4) równomiernie rozmieścić wkładki przez drzewo indeksu → nie ma „ogon”, ale lokalizacja pamięci podręcznej jest gorsza.
Czas sortowane (v7/ULID/Snowflake) są wstawione „w ogonie” → lepsza lokalizacja i kompresja, ale ryzyko gorących stron pod wysokim równoległym zapisu.
- prefiksy/shading przez lokatora/region (dodać 1-2 bajty przed czasem);
- przeplatanie: część losowości w wyższych bitach;
- wkładki wsadowe, fillfactor w drzewie B, automatyczne przejście do BRIN/klastrowania dla dużych kłód.
- „UUID (16B)” vs „BIGINT (8B) ”/„ INT8” pamięci/pamięci podręcznej; Base32/58/64 rzędy zwiększają rozmiar o 20-60%. Dla bazy danych, przechowywać binarne, serializować do łańcucha na krawędzi.
6) Bezpieczeństwo i prywatność
Nie używać SEQUENCE/INT jako publicznych identyfikatorów w URL/API: domyślne → wyliczenie zasobów.
Dodaj losowe, nieprzewidywalne identyfikatory (v4/v7/ULID/KSUID) dla odniesień zewnętrznych.
Nie koduj PII do identyfikatora. Jeśli chcesz włączyć atrybut, szyfruj/znak (na przykład JWE/JWS) lub użyj nieprzezroczystych żetonów.
Kodowanie URL: Base32 Crockford, Base58 (bez '0OIl'), Base64url.
7) Wielozadaniowość, prefiksy i routing
Format: '[LOKATOR _ PREFIX] - [ID]' lub binarne: 'lokator _ id | | id'.
Plusy: szybkie filtry/strony najemców, ochrona przed skanowaniem N + 1.
Minusy: może pogorszyć gęstość entropii w wyższych bitach → rozważyć dystrybucję (hash przedrostek).
Przyrostek hash (2-3 bajty) zmniejsza kolizje i pomaga odstrzelić routing: 'shard = hash (id)% N'.
8) Praktyczne zalecenia dotyczące wyboru
API, linki publiczne, usługi dystrybuowane bez ścisłego zamówienia: UUIDv4, ULID/KSUID.
Dzienniki/wydarzenia/zamówienia, gdzie często sortujemy według czasu: UUIDv7 lub ULID (monoton).
Ultra-wysoka przepustowość z lokalną monotonią i krótkim kluczem: 64-bitowy płatek śniegu (wymagana dyscyplina czasu).
Sklepienia artefaktów/budowli/blobs: adresowalne treści (SHA-256), a na górze - przyjazny dla człowieka krótki „pokaz” (Hashids/link).
Tabele lokalne w jednej bazie danych: SEQUENCE/IDENTITY + zewnętrzny „wrapper” dla linków publicznych (maskowanie).
9) Implementacje i przykłady
9. 1 PostgreSQL
Przechowywać UUID binarne, indeksy - 'btree' lub 'hash' w razie potrzeby.
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);
Sekwencyjny hot fix: dla sortowanego w czasie identyfikatora dodaj "sól' do górnych bitów lub wynik według najemcy:
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 (liczniki atomowe/monutonia)
bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20 (worker_id<<10) (seq & 1023)
9. 3 Generator płatków śniegu (pseudokoda)
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 w aplikacjach
Idź dalej
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())
Węzeł. 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) Kodowania i reprezentacje
Binarne w bazie danych ('BYTEA', 'UUID') → kompaktowe i szybkie. Na krawędzi przelicz na:- Base32 Crockford (ULID): przypadek nieczuły, brak wizualnie podobnych znaków.
- Base58: w skrócie Base32/64 dla żetonów czytelnych dla ludzi, URL-bezpieczne.
- Base64url: krótki, ale '-' i' _ 'w adresie URL.
Ustabilizować obudowę i format (łączniki/brak), aby uniknąć duplikatów podczas porównywania ciągów.
11) Testowanie odtwarzaczy i obserwowalność
Kolizje: metryczne 'id _ collision _ total' (musi być 0), alert na> 0.
Dystrybucja prefiksu: histogram wysokich bajtów - szukamy zakupu.
Szybkość generowania: 'ids _ per _ sec', opóźnienie generatora p99.
Zegar skew (na płatek śniegu): węzły offsetowe, „zegar wrócił” wydarzenia.
Ogony indeksowe: p95/p99 „INSERT” opóźnienie; odsetek zamków/gorących stron.
- Wtrysk „drift/back zegar” → upewnij się, że generator czeka/przełącza.
- „napływ” w milisekundach → czek next_ms.
- Masowy paralelizm → czy są burze zamków w indeksie.
12) Anty-wzory
AUTO_INCREMENT/SEQUENCE jako dowód publiczny: zgadnij, wycieki. Użyj publicznego nieprzezroczystego identyfikatora na wewnętrznej.
UUIDv1 (MAC/czas) out: prywatność.
64-bitowy przypadkowy identyfikator na bilion wpisów: realne ryzyko kolizji.
Globalny „centralny generator” bez HA: SPOF i wąskie gardło.
Identyfikatory sortowane w czasie bez ochrony pleców zegara: duplikaty/regresja zamówienia.
Mieszanie różnych formatów ID bez wyraźnej wersji/prefiksu → chaos w debacie/migracji.
Zapisywanie identyfikatora jako łańcucha z różnymi rejestrami/formularzami → ukryte duplikaty.
13) Lista kontrolna wdrażania
- Wybrany format (v4/v7/ULID/KSUID/Snowflake/SEQ/hash) dla wymagań domeny.
- Zdefiniowane wymagania dotyczące zamówień (czy wymagana jest sortowalność).
- Prawdopodobieństwo kolizji (b bity, n pokolenia) szacuje się i ustala próg ryzyka.
- Kodowanie jest zaprojektowane (binarne w prezentacji DB + czytelne dla ludzi).
- Dla sortowanych czasowo - ochrona pleców zegara, ograniczenia sekwencji i dyscyplina NTP/PTP.
- Dla identyfikatorów publicznych - nieprzewidywalność (losowa/ULID/KSUID), brak PII.
- Przemyślane hash (id)% N, prefiksy dla wielu najemców.
- Obserwowalność: kolizja, dystrybucja, opóźnienie, metryki skew zegara.
- Sekwencja/Contention/Skrzynki testowe z przelotem długości okna.
- Format, wersja, epoka, mapa bitowa i dokumentacja planu migracji.
14) FAQ
P: Co wybrać „domyślnie” dla mikroservices?
Odp.: UUIDv7 lub ULID: zamawianie czasu, dużo entropii, proste generowanie na krawędzi. Dla zewnętrznych API, ULID/UUIDv4 jest również ok.
P: Potrzebujesz krótkiego i czytelnego identyfikatora.
Odp.: Kodowanie ULID/KSUID lub Base58-128-bit losowe/tymczasowe. Pamiętaj o długości i kolizjach.
P: Czy możliwe jest „krótkie numeryczne” identyfikatory, ale bezpieczne?
Odp.: Tak: przechowywać wewnętrzny SEQ, a na zewnątrz dać nieprzezroczysty token (losowo 96-128 bitów) lub Hashids z solą + podpis.
P: Jak migrować z SEQ do UUIDv7?
Odp.: Wprowadź nową kolumnę 'id _ new' (UUID), dwutorową, opublikuj odniesienia do nowego identyfikatora, a następnie przełącz klucze DC/foreign i usuń stary.
P: Dlaczego moje wkładki ULID stały się „gorące”?
Odp.: Wstaw ściśle rosnące klucze do jednego indeksu. Partycja/najemca, wymieszać bity wysokiej klasy, używać wkładek wsadowych.
15) Kwoty całkowite
Dobrym identyfikatorem jest prawidłowy zestaw właściwości dla problemu: wystarczająca entropia, przewidywalne sortowanie (w razie potrzeby), bezpieczny rozgłos i zdrowe wykorzystanie indeksów. Wybierz UUIDv4/ULID/UUIDv7/KSUID dla prostoty i dystrybucji, płatek śniegu dla gęstej monotonii i krótkich klawiszy (dla dyscypliny czasu), sekwencje dla lokalnych tabel, hashes treści dla artefaktów. Określić obserwowalność i testy - i identyfikatory przestają być źródłem niespodzianek.