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): упорядочиваем, но несет 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 в одном тикe, координация битов 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 (MAC/время) наружу: приватность.
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, затем переключение PK/внешних ключей и удаление старого.

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

Нажимая кнопку, вы соглашаетесь на обработку данных.