Зачем они существуют

Случайный UUIDv4 превосходен в уникальности и ужасен в упорядоченности, и этот беспорядок тихо вредит производительности базы данных (история в статье ключи баз данных). Прежде чем UUIDv7 стандартизировал упорядоченный по времени UUID, несколько проектов изобрели собственные форматы идентификаторов ради той же выгоды: идентификатор, который сортируется по времени создания, компактен и дружелюбен к URL, и при этом не нуждается в центральной координации. Их стоит знать, потому что вы встретите их в реальных системах и потому что они иллюстрируют ту же горстку компромиссов.

Общий рецепт

Почти каждый сортируемый идентификатор использует одну идею: поместить метку времени первой и случайные или последовательные биты после. Сортировка идентификаторов как текста тогда упорядочивает их примерно по времени, потому что наиболее значимая часть — это часы. Кодирование всего в основании, сохраняющем порядок (так, чтобы текст сортировался так же, как байты), делает идентификаторы лексикографически сортируемыми как простые строки. Форматы различаются тем, сколько бит они тратят на время против случайности, как кодируют результат и нуждаются ли они в какой-либо координации между генераторами.

ULID

— это 128-битный идентификатор, той же ширины, что и UUID, отображаемый как 26 символов в base32 Крокфорда. Первые 48 бит — это миллисекундная метка времени, а оставшиеся 80 случайны. Кодирование нечувствительно к регистру и выбрано читаемым, и оно лексикографически сортируемо, с необязательным монотонным режимом, гарантирующим упорядоченность даже для идентификаторов, созданных в одну и ту же миллисекунду. ULID был одним из самых популярных ответов на проблему упорядочивания v4 и, будучи 128-битным, может храниться в том же объёме, что и UUID.

KSUID

KSUID (от Segment) — это 160-битный идентификатор, кодируемый как 27 символов base62. Он использует 32-битную метку времени с секундным разрешением и собственной эпохой (чтобы его метки времени оставались компактными многие годы), за которой следуют 128 бит случайности. Большая случайная часть делает коллизии исчезающе маловероятными даже при высоких темпах генерации, ценой того, что он шире UUID. Как и ULID, он сортируется по времени, когда сортируется как текст.

Snowflake

Snowflake (происходящий из Twitter) иной формы: 64-битное целое, а не 128-битное значение. Он упаковывает миллисекундную метку времени, идентификатор машины или дата-центра и порядковый номер на миллисекунду в 64 бита, так что помещается в стандартный знаковый столбец bigint. Эта компактность — его привлекательность для очень больших наборов данных. Загвоздка — координация: каждому генератору нужен уникальный ID машины, который надо назначить и которым надо управлять, так что Snowflake меняет свободное от координации свойство UUID на куда меньший идентификатор. У многих систем есть собственная схема в стиле Snowflake, построенная на той же идее упаковки.

NanoID

NanoID — исключение: он вовсе не основан на времени. Это просто компактная, безопасная для URL случайная строка с настраиваемым алфавитом и длиной, спроектированная как меньшая, более дружелюбная альтернатива случайному UUID, когда вам нужен непрозрачный идентификатор в короткой строке. Сравнивайте его с UUIDv4, а не с сортируемыми форматами: он оптимизирован под размер и дружелюбие к URL, а не под упорядочивание.

Как они сравниваются

Компромиссы выстраиваются по нескольким осям. Размер: Snowflake — 64-битный, ULID и UUID — 128-битные, KSUID — 160-битный. Кодирование: base32 (ULID), base62 (KSUID), целое (Snowflake) или шестнадцатеричное (UUID). Координация: только Snowflake нуждается в назначенных ID машин; остальные свободны от координации. Сортируемость: все, кроме NanoID, упорядочены по времени. И общая оговорка: любой идентификатор с префиксом-меткой времени делает своё время создания видимым каждому, кто может его прочесть, что приемлемо для большинства применений, но стоит отметить, когда идентификатор раскрывается публично.

Почему UUIDv7 изменил расчёт

Причина, по которой этот список значит меньше, чем прежде, в том, что UUIDv7 (см. статью версии) стандартизировал тот же рецепт метка-времени-плюс-случайность внутри обычного 128-битного формата UUID. Он даёт вам упорядочивание по времени и хорошую локальность индекса, оставаясь при этом стандартным UUID, который каждая база данных и библиотека уже понимают, без своего кодирования и без координации. Поэтому для новых систем честный выбор по умолчанию — обратиться сначала к v7 и использовать одну из этих альтернатив, только когда вам нужно её специфическое свойство: 64-битная ширина, помещающаяся в bigint (Snowflake), особое текстовое кодирование или большее случайное пространство. Они решили реальную проблему; v7 теперь решает большую её часть в стандартной форме.