Webrium
Подписаться

UUID или автоинкремент: чем платят за каждый

Как выбрать первичный ключ для базы данных и избежать проблем с производительностью и масштабированием

Сравнение автоинкремента и UUID: разделённое поле с пересекающими границу элементамиАвтоинкрементUUID

Что первичный ключ обязан обеспечивать: невидимая арифметика хранилища

Первичный ключ — это не просто техническая формальность. Он определяет, как данные будут физически храниться, как быстро к ним можно будет обратиться и насколько эффективно система сможет масштабироваться. Вот что он обязан гарантировать:

  1. Уникальность без компромиссов: ни одна запись не должна совпадать с другой по ключу, иначе нарушается целостность данных.
  2. Стабильность на всём жизненном цикле: ключ не должен меняться после создания, иначе все ссылки на запись (внешние ключи, кэши, URL) сломаются.
  3. Эффективность индексации: ключ напрямую влияет на размер и скорость работы индексов — а значит, на производительность всей базы.
  4. Локальность хранения: как записи располагаются на диске. Если ключ выбран неудачно, даже простая вставка может превратиться в операцию с несколькими случайными обращениями к диску.

Выбор ключа — это не вопрос вкуса, а инженерное решение, от которого зависит, сколько ресурсов потребуется для хранения и обработки данных.

Автоинкремент: простота, которая работает — пока не перестаёт

Автоинкрементный ключ (INT или BIGINT) — первое, что приходит в голову. Он:

  • Компактный: 4 или 8 байт на запись. Для таблицы с миллионом строк это разница между 4 МБ и 16 МБ на индекс.
  • Упорядоченный: новые записи получают последовательные значения, что улучшает локальность хранения. Записи, созданные в одно время, физически лежат рядом на диске.
  • Дружелюбен к индексам: B-деревья оптимизированы для монотонно возрастающих значений. Вставка в конец дерева требует минимальных операций.

Но у автоинкремента есть фундаментальные ограничения:

  1. Раскрывает порядок и объём данных: по значению ключа можно оценить, сколько записей уже создано и в каком порядке. Это может быть проблемой для публичных API или систем, где порядок создания не должен быть известен.
  2. Зависит от базы: ключ генерируется на стороне СУБД, что усложняет слияние данных из нескольких источников. Если у вас есть две базы, которые должны обмениваться данными, автоинкремент заставит вас либо синхронизировать генераторы ключей, либо придумывать обходные пути.
  3. Проблемы с распределёнными системами: если несколько баз или сервисов генерируют ключи независимо, коллизии неизбежны. Даже если вы используете разные диапазоны (например, первая база генерирует ключи от 1 до 1 000 000, вторая — от 1 000 001), это не масштабируется.

Автоинкремент — хороший выбор для монолитных приложений, где база одна, а данные не нужно объединять с другими источниками. Но как только появляется необходимость в распределённости, он становится источником проблем.

Случайный идентификатор: свобода ценой производительности

Случайные идентификаторы (например, UUID) решают проблемы автоинкремента:

  • Генерируются где угодно: можно создать ключ на клиенте, в микросервисе или в очереди сообщений. Это упрощает архитектуру распределённых систем.
  • Ничего не раскрывают: по значению UUID нельзя понять, сколько записей существует или в каком порядке они создавались. Это делает его безопасным для публичных API.
  • Подходят для распределённых систем: коллизии крайне маловероятны (вероятность коллизии для UUIDv4 — порядка 1 к 2^122).

Но за эти преимущества приходится платить:

  1. Размер: UUID занимает 16 байт. Для таблицы с миллионом записей это 16 МБ на индекс — в 4 раза больше, чем для INT, и в 2 раза больше, чем для BIGINT.
  2. Плохая локальность: случайные значения плохо ложатся в B-деревья. Индекс начинает фрагментироваться, что увеличивает количество операций ввода-вывода при вставках и выборках.
  3. Раздутые вторичные индексы: если внешний ключ ссылается на UUID, он тоже занимает 16 байт. Для таблицы с миллионами записей это может быть критично: каждый внешний ключ увеличивает размер индекса на 16 МБ.

UUID — хороший выбор для распределённых систем, где данные создаются в разных местах, но не подходит для высоконагруженных таблиц с частыми вставками.

Упорядоченные во времени идентификаторы: компромисс, который не всегда работает

Есть решения, которые пытаются объединить преимущества автоинкремента и UUID. Например:

  • ULID (Universally Unique Lexicographically Sortable Identifier): первые 48 бит — временная метка, оставшиеся 80 бит — случайность. Упорядочен по времени, но генерируется где угодно.
  • Snowflake ID: 64-битный идентификатор, где часть бит отвечает за время, часть — за идентификатор машины, часть — за порядковый номер.

Преимущества таких идентификаторов:

  • Упорядоченность: записи можно сортировать по времени создания, что полезно для аналитики и кэширования.
  • Генерация вне базы: подходит для распределённых систем.
  • Компактнее UUID: обычно 8–16 байт.

Но у них есть свои недостатки:

  1. Сложнее в реализации: нужно следить за уникальностью генераторов. Если два сервиса используют один и тот же идентификатор машины, возможны коллизии.
  2. Раскрывает время создания: если это критично (например, для публичных API), может быть проблемой.
  3. Не решает проблему размера: 8–16 байт всё ещё больше, чем 4 байта автоинкремента.

Такие идентификаторы — хороший выбор для систем, где важна сортировка по времени, но нужна распределённость. Но если вам не нужна упорядоченность, они могут оказаться избыточными.

Где это заметно: индексы, вставки, внешние ключи

Выбор ключа влияет на несколько аспектов работы базы. Давайте разберём их подробнее.

Объём индексов

  • Автоинкремент: индекс компактный, так как значения монотонно возрастают. B-дерево остаётся сбалансированным, и новые записи добавляются в конец.
  • UUID: индекс раздувается из-за случайных значений. B-дерево начинает фрагментироваться, и каждая вставка может требовать перебалансировки. Для таблицы с миллионом записей это может означать в 2–3 раза больше операций ввода-вывода.

Скорость вставки пачками

  • Автоинкремент: быстрая вставка, так как новые записи добавляются в конец индекса. Это минимизирует количество операций с диском.
  • UUID: медленнее, так как случайные значения заставляют индекс часто перебалансироваться. В худшем случае вставка одной записи может потребовать нескольких случайных обращений к диску.

Размер внешних ключей

  • Если внешний ключ ссылается на UUID, он тоже занимает 16 байт. Для таблицы с миллионами записей это может быть критично: каждый внешний ключ увеличивает размер индекса на 16 МБ. Для сравнения, если бы ключ был INT, индекс занял бы всего 4 МБ.

Локальность хранения

  • Автоинкремент: записи физически располагаются рядом, что ускоряет последовательные выборки. Например, если вы запрашиваете записи с ключами от 1 до 1000, они, скорее всего, будут лежать на одной странице диска.
  • UUID: записи разбросаны по диску, что замедляет работу. Даже простая выборка может потребовать нескольких случайных обращений к диску.

Ключ в адресе страницы: почему это вопрос не только базы

Иногда ключ используется не только внутри базы, но и в URL. Например:

  • https://example.com/users/123 (автоинкремент)
  • https://example.com/users/550e8400-e29b-41d4-a716-446655440000 (UUID)

Проблемы с автоинкрементом в URL:

  • Раскрывает порядок создания записей. Например, если вы видите URL users/42, вы можете предположить, что это 42-й зарегистрированный пользователь.
  • Позволяет перебирать идентификаторы. Например, можно попробовать users/1, users/2 и так далее, чтобы получить список всех пользователей.

Проблемы с UUID в URL:

  • Длинные и нечитаемые адреса. Например, 550e8400-e29b-41d4-a716-446655440000 сложно запомнить и передать.
  • Сложнее отлаживать. Если в логах появляется ошибка с UUID, его сложно воспроизвести вручную.

Решения:

  1. Использовать короткие хеши: например, base64-кодированный UUID. Это сокращает длину URL, но не решает проблему читаемости.
  2. Добавлять в URL не ключ, а другой уникальный атрибут: например, имя пользователя. Это делает URL более читаемым, но требует дополнительной логики для обработки коллизий (например, если два пользователя захотят зарегистрироваться с одним именем).

Тупик: выбрать по красоте адреса

Первое, что приходит в голову при выборе ключа, — это эстетика. Например:

  • “UUID выглядит круче и современнее”.
  • “Автоинкремент слишком простой и скучный”.
  • “Хочу, чтобы URL были красивыми и короткими”.

Но красота адреса — это не критерий. Если UUID портит производительность, а автоинкремент не подходит для распределённой системы, нужно искать компромисс. Вот что обычно пробуют первым и почему это не работает:

  1. Использовать UUID везде: кажется, что это решит все проблемы с распределённостью. Но на практике UUID замедляет вставки и увеличивает размер индексов. Для высоконагруженных систем это может стать узким местом.
  2. Использовать автоинкремент и синхронизировать генераторы ключей: кажется, что это решит проблему слияния данных. Но на практике синхронизация генераторов ключей между несколькими базами — это сложная задача, которая требует дополнительной инфраструктуры и может стать источником ошибок.
  3. Использовать короткие хеши в URL: кажется, что это сделает адреса красивее. Но на практике хеши всё ещё нечитаемы, и их сложно отлаживать.

В итоге приходится возвращаться к инженерным соображениям: что важнее — производительность, распределённость или читаемость URL? И искать компромисс, который удовлетворит все требования.

Позиция редакции

Мы считаем, что выбор первичного ключа должен основываться на архитектуре системы, а не на личных предпочтениях. Вот как мы рекомендуем подходить к вопросу:

  1. Монолитное приложение с одной базой? Автоинкремент — лучший выбор. Он компактный, быстрый и простой в реализации. Если вам не нужно объединять данные с другими источниками, нет смысла усложнять систему.
  2. Распределённая система с несколькими источниками данных? UUID или упорядоченный идентификатор (например, ULID) — единственный вариант. Но будьте готовы к тому, что это увеличит размер индексов и замедлит вставки.
  3. Высокая нагрузка на вставки? Избегайте UUID. Он замедлит работу и увеличит нагрузку на диск. Если вам нужна распределённость, рассмотрите упорядоченные идентификаторы.
  4. Ключ используется в URL? Подумайте о коротком хеше или другом уникальном атрибуте (например, имени пользователя). Но не забывайте о проблемах с коллизиями и читаемостью.

Эта позиция неверна, если:

  • Ваша система не подходит ни под один из описанных сценариев.
  • У вас есть специфические требования (например, юридические ограничения на раскрытие порядка создания записей).
  • Ваша нагрузка нетипична (например, вы вставляете данные пачками по миллиону записей, но редко их читаете).

Как проверить свой выбор

Если вы всё ещё не уверены, какой ключ выбрать, проведите эксперимент:

  1. Создайте тестовую таблицу с автоинкрементным ключом и заполните её миллионом записей.
  2. Измерьте скорость вставки и размер индексов.
  3. Повторите эксперимент с UUID.
  4. Сравните результаты.

Такой подход поможет вам принять взвешенное решение, основанное на данных, а не на предположениях.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Совпадение индекса с запросом и его отсутствиеИндекс подходитИндекс не подходит
Предыстория

Индексы в базе: какие ставить и когда они не спасают

Почему UUID и автоинкремент влияют на скорость запросов — и когда индексы не решают проблему.

Стык версий приложения и базы: совместимость vs конфликтСовместимая схемаНесовместимая схема
Углубление

Миграции базы без простоя: порядок, который работает

Как обеспечить работу приложения с базой во время изменений схемы — без простоев и сбоев.

Два стыка: расчёт по модели из пяти слоёв совпадает с реальной стоимостью месяца, а прайс, умноженный на объём, с ней расходитсямодель из слоёвпрайс × объём
Усиление

Стоимость хранения: три сценария на 100 ТБ

Как реально сравнить стоимость хранения на своих дисках, арендованном сервере и в облаке — без упрощений и с учётом всех скрытых слоёв.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть