Что такое Вероятность коллизии UUID
Интуиция «одна коллизия на 2122 значений» неверна — и ошибается в вашу пользу. Совпадение возможно между любыми двумя значениями, поэтому риск растёт как квадрат их количества. Стандартное приближение: p ≈ n² / (2 × 2122). Отсюда и известная цифра: около 2.7 × 1018 значений — примерно три триллиона в секунду в течение года — прежде чем вероятность дубля достигнет 50%.
На реальных объёмах числа успокаивают: миллиард UUID v4 даёт вероятность около 9 × 10-20. Но на практике важны ещё две вещи. Во-первых, энтропия не лучше генератора: UUID на Math.random(), на сломанном ГПСЧ или с фиксированным зерном имеет куда меньше эффективных бит, чем предполагает формат. Во-вторых, формулы предполагают независимость: если два сервиса создают значения из одного и того же засеянного источника, граница дней рождения к ним неприменима.
Короткие идентификаторы — как раз тот случай, когда решение становится реальным. Та же математика с 64 битами вместо 122 означает, что миллиард значений уже несёт примерно 2.7% вероятности коллизии. Это не повод отказываться от компактных id — это повод подбирать их под задачу и держать в базе ограничение уникальности как последнюю линию защиты.
Как использовать
- Посчитайте, сколько идентификаторов понадобится всего, включая исторические строки и импорт из других систем.
- Найдите соответствующую вероятность в таблице ниже.
- Если коллизия дорого обойдётся, оставьте ограничение UNIQUE: математика снижает шансы, но не отменяет необходимости в ограничении.
Сценарии
- Выбор между UUID и коротким id, где 64 бита могут быть достаточны для кеша и слишком малы для реестра.
- Ревизия чужого кода, который сам создаёт идентификаторы: проверять нужно источник энтропии, а не формат.
- Объяснение заказчику, почему «случайный» не означает «ограничение не нужно».
Сравнение Вероятность коллизии UUID с другими форматами
| Вариант | Когда использовать |
|---|---|
10<sup>6</sup> значений | ≈ 9.4 × 10<sup>-26</sup> — практически ноль. |
10<sup>9</sup> значений | ≈ 9.4 × 10<sup>-20</sup>. |
10<sup>12</sup> значений | ≈ 9.4 × 10<sup>-14</sup>. |
10<sup>15</sup> значений | ≈ 9.4 × 10<sup>-8</sup> — примерно один шанс из десяти миллионов. |
2.7 × 10<sup>18</sup> значений | ≈ 50% — точка, где монетка была бы не хуже. |
64-битные id, 10<sup>9</sup> значений | ≈ 2.7% — та же математика, но на 58 бит меньше. |
Примеры кода
Вероятность в Python
from math import exp
def collision_probability(n, bits=122):
N = 2 ** bits
return 1 - exp(-(n * n) / (2 * N))
for n in (10**6, 10**9, 10**12, 10**15, 2.7 * 10**18):
print(f'{n:.0e}', collision_probability(n))
# 64-битные id достигают 2.7% на миллиарде значений
print(collision_probability(10**9, bits=64))
То же в одну строку (JavaScript)
const p = (n, bits = 122) => 1 - Math.exp(-(n * n) / (2 * 2 ** bits));
p(1e9); // ~9.4e-20
p(1e9, 64); // ~0.027
Ограничение в схеме
CREATE TABLE events (
id uuid PRIMARY KEY, -- или UNIQUE NOT NULL для не-ключевого поля
created_at timestamptz NOT NULL DEFAULT now()
);
-- Математика делает коллизии маловероятными, ограничение — невозможными.
Частые вопросы
Случались ли реальные коллизии UUID v4?
Случайно и в заметных масштабах — нет, вероятность слишком мала. Почти все сообщения о дублях объясняются обрезанным полем, фиксированным или повторно использованным зерном, копированием в тестовых данных или генератором, который вовсе не был случайным (например, метка времени с низким разрешением).
Нужно ли всё равно добавлять UNIQUE?
Да. Вероятностный аргумент говорит, что коллизия маловероятна; ограничение определяет, что база сделает, если она всё-таки случится. Оно стоит одного индекса и превращает тихий баг качества данных в явную ошибку — именно это и нужно.
Как изменятся шансы, если укоротить идентификатор?
Они растут как квадрат объёма и падают экспоненциально с числом бит. Урезав 128 бит до 64, на миллиарде значений вы получите 2.7% вместо 9 × 10-20: это всё ещё приемлемо для ключа кеша с коротким сроком жизни и совершенно недостаточно для идентификатора, который клиент хранит годами.