GH GambleHub

Генерація ідентифікаторів

1) Навіщо приділяти увагу ідентифікаторам

Ідентифікатор (ID) - фундаментальний ключ сутності: рядки БД, повідомлення, файл, замовлення. Від його властивостей залежать:
  • Унікальність і масштаб (колізії, горизонтальний ріст).
  • Порядок і сортування (тимчасова кореляція, реплікація, дедуп).
  • Продуктивність зберігання (індекси, гарячі сторінки, розмір ключа).
  • Безпека (непередбачуваність, витоки, вгадування).
  • Юзабіліті/інтеграції (короткий, URL-safe, не чутливий до регістру).

Вибір ID - компроміс між ентропією, впорядковуваністю, довжиною, швидкістю генерації та експлуатацією.

2) Ключові вимоги та терміни

Унікальність: ймовірність колізії повинна бути нижче прийнятного ризику.
Ентропія: «скільки випадковості» містить ID (біт).
Впорядковуваність (time-sortable/k-sortable): лексикографічне сортування ≈ сортування за часом.
Монотонія: невпинна послідовність всередині вузла/потоку.
Локальність запису: наскільки нова вставка концентрується в «хвості» індексу (небезпека гарячих сторінок).
Передбачуваність: чи можна вгадати сусідні ID (важливо для безпеки/API).
Представлення: бінарний/рядковий, Base16/32/36/58/64, дефіси, регістр.

3) Основні сімейства ідентифікаторів

3. 1 UUID

v4 (random): 122 біти ентропії. Невпорядковуємо, хороший для безпеки і простоти. Мінус: «хаотит» індекси через випадковий розподіл - що, однак, рівномірно розсіює навантаження і прибирає «гарячі сторінки».
v1 (time + MAC): впорядковуємо, але несе МАС/час (приватність); часто уникають.
v7 (time-ordered): мілісекундний час + рандомна частина. Дизайн під лексикографічне сортування за часом і хорошу компресію в БД. Компроміс: з'являється «гарячий хвіст» індексу; лікується шардуванням/префіксами/інкрементом.

Поради

Для зовнішніх API і нестрогих вимог до порядку - v4.
Для подієвих/лігвих БД і «сортованих» ключів - v7.

3. 2 ULID (Crockford Base32)

128 біт: 48 біт часу (мс) + 80 біт випадковості. Лексикографічно сортується за часом, людино-доброзичливий (без'I, L, O, U'), URL-safe. Є монотонна варіація (при однаковій мітці часу випадкова частина збільшується).
Плюси: читаність, впорядковуваність, переносимість.
Мінуси: при дуже високій частоті вставок в один момент часу - «гарячий хвіст».

3. 3 KSUID

160 біт: 32 біт часу (сек) щодо епохи + 128 біт випадковості. Більший часовий діапазон і стабільна сортованість, рядки коротше ULID? (ні - довше, але зі своїм кодуванням), хороший для розподілених логів і об'єктів.

3. 4 Snowflake-подібні (k-sortable flake IDs)

Класична схема (настроюється):

[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]

Властивості: монотонний ріст на вузлі, квазі-глобальна унікальність, коротке (64 біта) бінарне уявлення.
Ризики: залежність від годинника (дрейф/регрес часу), вичерпання sequence в одному тику, координація бітів region/worker.
Лікується: захистом від «clock back», резервом sequence, детектором часу, PTP/NTP дисципліною.

3. 5 Послідовності БД (SEQUENCE/IDENTITY)

Найпростіша монотонна генерація в одній СУБД/шарді.
Плюси: коротко, швидко, зручно для локальних таблиць.
Мінуси: важко глобально в розподіленому кластері; передбачувано (небезпечно як публічний ключ), створює гарячий хвіст індексу.

3. 6 Контент-адресні ID (hash content)

SHA-256/Blake3 від вмісту → стабільний ID, дедуплікація, перевірка цілісності, кешування.
Плюси: детермінізм, захист від підміни.
Мінуси: дорога генерація (CPU), колізії практичні нулі, немає тимчасового сортування, довжина.

4) Колізії та «парадокс днів народження» (інтуїтивно)

Ймовірність колізії для випадкового ID розміру «b» біт при «n» генераціях наближено:

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) вставляються «у хвіст» → краща локальність і компресія, але ризик гарячих сторінок під високим паралельним записом.

Пом'якшення «гарячого хвоста»:
  • префікси/шардинг по tenant/region (додати 1-2 байта перед часом);
  • interleaving: частина випадковості в старших бітах;
  • батч-вставки, fillfactor в B-дереві, автоперехід на BRIN/кластеризацію для великих логів.
Розмір важливий:
  • 'UUID (16B)'vs'BIGINT (8B) '/' INT8'економить пам'ять/кеш; рядки Base32/58/64 збільшують розмір на 20-60%. Для БД зберігайте бінарно, серіалізуйте в рядок на краю.

6) Безпека і приватність

Невикористовуйте SEQUENCE/INT як публічні ID в URL/API: вгадувані → перерахування ресурсів.
Додавайте рандомні, непередбачувані ID (v4/v7/ULID/KSUID) для зовнішніх посилань.
Не кодуйте PII в ID. Якщо потрібно включити атрибут - шифруйте/підписуйте (наприклад, JWE/JWS) або використовуйте непрозорі токени.
URL-безпечні кодування: Base32 Crockford, Base58 (без `0OIl`), Base64url.

7) Мульти-тенантність, префікси і маршрутизація

Формат: '[TENANT _ PREFIX] - [ID]'або бінарно: `tenant_id || id`.
Плюси: швидкі фільтри/партії по орендарю, захист від N + 1 сканів.
Мінуси: може погіршити щільність ентропії в старших бітах → продумайте розподіл (хеш префікса).
Hash-суфікс (2-3 байти) знижує колізії і допомагає shard-роутингу: `shard = hash(id) % N`.

8) Практичні рекомендації щодо вибору

API, публічні посилання, розподілені сервіси без суворого порядку: UUIDv4, ULID/KSUID.
Логи/події/замовлення, де часто сортуємо за часом: UUIDv7 або ULID (монотонний).
Надвисока пропускна з локальною монотонністю і коротким ключем: Snowflake-подібний 64-біт (потрібна дисципліна часу).
Сховища артефактів/білдів/блобів: контент-адресні (SHA-256), а поверх - людино-доброзичлива коротка «вітрина» (Hashids/посилання).
Локальні таблиці в одній БД: SEQUENCE/IDENTITY + зовнішня «обгортка» для публічних посилань (masking).

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);
Sequential hot fix: для time-sorted ID додайте «сіль» в старші біти або партицируйте по 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 (атомарні лічильники/монутонія)

bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20     (worker_id<<10)      (seq & 1023)

9. 3 Snowflake-подібний генератор (псевдокод)

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())

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) Кодування та представлення

Бінарно в БД («BYTEA», «UUID») → компактно і швидко. На краю конвертуйте в:
  • Base32 Crockford (ULID): нечутлива до регістру, без візуально схожих символів.
  • Base58: коротше Base32/64 для людиночитаних токенів, URL-safe.
  • Base64url: коротко, але'-'і'_'в URL.

Стабілізуйте регістр і формат (дефіси/їх відсутність), щоб уникнути дублікатів при порівнянні рядків.

11) Тест-плейбуки та спостережуваність

Колізії: метрика'id _ collision _ total'( повинна бути 0), алерт при> 0.
Розподіл префіксів: гістограма старших байтів - шукаємо скоплення.
Швидкість генерації: 'ids _ per _ sec', p99 латентності генератора.
Clock skew (для Snowflake): офсет вузлів, події «clock went back».
Індексні хвости: p95/p99 `INSERT` latency; частка блокувань/гарячих сторінок.

Game day:
  • Інжект «clock drift/back» → переконуємося, що генератор чекає/перемикається.
  • Переповнення «sequence» в мілісекунді → перевірка очікування next_ms.
  • Масовий паралелізм → чи немає штормів блокувань в індексі.

12) Анти-патерни

AUTO_INCREMENT/SEQUENCE як публічний ID: вгадується, витоку. Використовуйте публічний непрозорий ID поверх внутрішнього.
UUIDv1 (МАС/час) назовні: Приватність.
64-біт випадковий ID на трильйони записів: реальний ризик колізій.
Глобальний «центральний генератор» без HA: SPOF і вузьке місце.
Time-sorted IDs без захисту від clock back: дублікати/регрес порядку.
Змішування різних форматів ID без явної версії/префікса → хаос в дебазі/міграціях.
Збереження ID як рядки з різними регістрами/формами → приховані дублікати.

13) Чек-лист впровадження

  • Обраний формат (v4/v7/ULID/KSUID/Snowflake/SEQ/hash) під доменні вимоги.
  • Визначені вимоги до порядку (чи потрібна сортованість).
  • Оцінена ймовірність колізій (b біт, n генерацій) і заданий поріг ризику.
  • Спроектоване кодування (бінарно в БД + людиночитана вітрина).
  • Для time-sorted - захист від clock back, sequence-ліміти і NTP/PTP дисципліна.
  • Для публічних ID - непередбачуваність (рандом/ULID/KSUID), відсутність PII.
  • Продуманий шард-роутинг (hash (id)% N), мульти-тенантні префікси.
  • Спостережуваність: метрики колізій, розподілу, затримок, clock skew.
  • Тест-кейси на переповнення sequence/високу конкуренцію/довжину вікна.
  • Документація формату, версії, епохи, бітової розмітки і план міграцій.

14) FAQ

Q: Що вибрати «за замовчуванням» для мікросервісів?
A: UUIDv7 або ULID: впорядковуваність за часом, багато ентропії, проста генерація на краю. Для зовнішніх API - ULID/UUIDv4 теж бл.

Q: Потрібен короткий і людиночитаний ID.
A: ULID/KSUID або Base58-кодування 128-біт випадкового/тимчасового ID. Пам'ятайте про довжину і колізії.

Q: Чи можна зробити «короткі числові» ID, але безпечно?
A: Так: зберігайте внутрішній SEQ, а назовні віддавайте opaque токен (рандом 96-128 біт) або Hashids з сіллю + підпис.

Q: Як мігрувати з SEQ на UUIDv7?
A: Введіть новий стовпець'id _ new'( UUID), двупись, публікація посилань на новий ID, потім перемикання РК/зовнішніх ключів і видалення старого.

Q: Чому мої вставки з ULID стали «гарячими»?
A: Вставляєте строго зростаючі ключі в один індекс. Розбийте по партіях/tenant, перемішайте старші біти, використовуйте batch-вставки.

15) Підсумки

Хороший ID - це правильний набір властивостей під задачу: досить ентропії, передбачуване сортування (якщо потрібна), безпечна публічність і здорова експлуатація індексів. Вибирайте UUIDv4/ULID/UUIDv7/KSUID для простоти і розподіленості, Snowflake - для щільної монотонії і коротких ключів (при дисципліні часу), послідовності - для локальних таблиць, хеші контенту - для артефактів. Закладайте спостережуваність і тести - і ідентифікатори перестануть бути джерелом сюрпризів.

Contact

Зв’яжіться з нами

Звертайтеся з будь-яких питань або за підтримкою.Ми завжди готові допомогти!

Telegram
@Gamble_GC
Розпочати інтеграцію

Email — обов’язковий. Telegram або WhatsApp — за бажанням.

Ваше ім’я необов’язково
Email необов’язково
Тема необов’язково
Повідомлення необов’язково
Telegram необов’язково
@
Якщо ви вкажете Telegram — ми відповімо й там, додатково до Email.
WhatsApp необов’язково
Формат: +код країни та номер (наприклад, +380XXXXXXXXX).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.