Как генерировать уникальные идентификаторы в распределённой системе?
- Как устроен Snowflake ID?
- UUID или автоинкремент?
- Почему UUIDv4 плох как первичный ключ?
Варианты: автоинкремент (просто, но одна точка и угадываемость), UUIDv4 (без координации, но случайный — плохо для B-дерева), UUIDv7 / Snowflake (время в старших битах — почти упорядочены, без координации).
- Snowflake: 41 бит времени + 10 бит узла + 12 бит счётчика = 64 бита, 4096 id в мс на узел
- UUIDv7: 48 бит времени в мс + случайность, 128 бит, стандарт RFC 9562
- Выдача диапазонов из базы — компромисс для автоинкремента
А ещё Риски Snowflake: откат часов и уникальность номера узла
Подробно
Требования: уникальность без центральной точки на каждую вставку, высокая скорость, желательно — сортировка по времени создания и компактность, иногда — неугадываемость.
Автоинкремент базы. Просто, компактно, упорядоченно. Минусы: при шардировании нужны разные диапазоны или шаги (auto_increment_offset), генерация привязана к базе (id известен только после вставки), id раскрывают объём бизнеса и позволяют перебирать ресурсы.
Выдача диапазонов. Сервис берёт у базы блок из 1000 номеров (UPDATE seq SET value = value + 1000 RETURNING value) и выдаёт из памяти. Быстро, координация редкая; при падении теряется хвост блока (дырки в последовательности — нормально).
UUIDv4 — 122 случайных бита, генерируется где угодно без координации, вероятность коллизии пренебрежимо мала. Минусы: 16 байт; случайный порядок — каждая вставка в B-дерево индекса попадает в случайную страницу: больше разделений страниц, хуже кэш, больше WAL. На больших таблицах вставка заметно медленнее последовательного ключа.
Упорядоченные по времени:
- Snowflake (Twitter): 64 бита = знак + 41 бит миллисекунд от своей эпохи (~69 лет) + 10 бит номера узла (1024 узла) + 12 бит счётчика в пределах миллисекунды (4096 id). Помещается в bigint, сортируется по времени. Нужно: уникально раздать номера узлов (конфиг, ZooKeeper/etcd, аренда), обработать откат часов (NTP) — ждать или отказывать, если время пошло назад, и переполнение счётчика в миллисекунде — ждать следующую;
- UUIDv7 (RFC 9562, 2024) — 48 бит времени в миллисекундах + версия + случайные биты. Без координации и номеров узлов, почти монотонен, хорош для индексов; поддерживается в PostgreSQL 18 (
uuidv7()) и библиотеках; - ULID, KSUID — похожие идеи с текстовым представлением.
Неугадываемость. Упорядоченные id раскрывают время создания и плотность. Для публичных ссылок — отдельный случайный публичный токен или обфускация, а не опора на секретность id.
Как выбрать: одна база и внутренние id — автоинкремент или bigint identity; распределённая генерация и индексы — UUIDv7 или Snowflake; нужен короткий публичный код — base62 от числа с перемешиванием.
Пример
import time, threading
class Snowflake:
EPOCH = 1_704_067_200_000 # 2024-01-01 в мс
def __init__(self, node):
assert 0 <= node < 1024
self.node, self.seq, self.last, self.lock = node, 0, -1, threading.Lock()
def next(self):
with self.lock:
now = int(time.time() * 1000)
if now < self.last:
raise RuntimeError("часы пошли назад")
if now == self.last:
self.seq = (self.seq + 1) & 0xFFF
if self.seq == 0: # 4096 id в этой мс — ждём следующую
while now <= self.last: now = int(time.time() * 1000)
else:
self.seq = 0
self.last = now
return ((now - self.EPOCH) << 22) | (self.node << 12) | self.seqЧто хотят услышать
- Варианты и их компромиссы: автоинкремент, диапазоны, UUIDv4, Snowflake, UUIDv7.
- Почему случайный UUID плох для B-дерева.
- Устройство Snowflake и его риски: часы, номер узла.
- Вопрос угадываемости публичных id.
Частые ошибки
- Брать UUIDv4 первичным ключом огромной таблицы и не понимать, почему вставка медленная.
- Генерировать id из времени без счётчика и номера узла.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗UUIDv4 почти упорядочен по времени
- ✗Автоинкремент не имеет недостатков в распределённой системе
- ✓UUIDv7 или Snowflake: время в старших битах, без координации
- ✗Snowflake требует центрального сервера на каждый id
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.