Как стать автором
Обновить

Комментарии 90

А где же недостатки? Получается что кто числа использует - все бестолковые? Главный недостаток на мой взгляд что они очень уж жирные.

еще возможный недостаток - что не создают некоторого естественного упорядочения записей.

ULID вроде бы для этого

Для  естественного упорядочения записей в таблицу вводится новая колонка CreatedDateTime это записи и индекс по этой колонке.

Несмотря на полезность такого поля, это все же костыль, так как может совпадать у нескольких строк. Значит детерминированный порядок выборки записей обеспечить не может. Иногда это важно.

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

А зачем тогда мне UUID, если все равно bigserial есть? Ну кроме тех случаев, когда глобальный идентификатор действительно необходим для каких-то внешних целей.

Вот кстати да, если уж реально надо внешний ID, можно сделать отдельную колонку external_id для него и пихать туда что лучше подходит - рандомную строку, UUID, да что угодно. Можно даже генерить на лету и по запросу, если данные нужны относительно редко. И если доступ надо убрать - можно этот внешний ИД обнулить.

эээ, а uuid тогда зачем?)

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

главный недостаток - они не влазят в голову.

Второй главный - выходит из плюсов. Они не предсказуемы, что делает возможным, и даже "нормальным" появление дублей. Можно "починить" генерацией их как хэша от логического ключа. Но это будет не uuid по спецификации, а просто тип данных uuid, если таковой поддерживается бд.

Третий недостаток - в зависимости от метода генерации они таки могут повторятся, особенно в контейнерах.

Четвертое - генерация ид централизованно обычно вообще не беда. Это очень быстрая операция. Да хоть отдельную посгрю под это завести, обычно это не доставит проблем.

Пятое - мультимастер на запись не простая штука, задачу византийских генералов никто не отменял. В большинстве случаев консистентность гораздо важнее.

Шестое - в PostgreSQL gen_random_uuid() медленней bigserial в два раза даже на одном потоке. А так как доступ к энтропии производится через глобальную блокировку, то на нескольких потоках может еще медленней оказаться.

Все эти аргументы являются ложными для версии UUIDv7, устраняющей недостатки прежних версий. Читайте стандарт: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/ Насчет первого аргумента: пользуйтесь SQL, поиском и копипастой

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

use Illuminate\Support\Str;

class Uuid
{
    public static function generate(Model|string $model): string
    {
        do {
            $uuid = Str::uuid();
        } while ($model::whereKey($uuid)->exists());

        return $uuid;
    }
}

И, на практике с таблицей примерно в 2к записей бывали случаи когда уникальный идентификатор подбирался за 2, а то и за 3 прохода. В основном это случалось в момент массовой обработки данных по 500-1000 элементов.

Во-первых, такое ощущение, что Вы не читали, что Вам пишут. Главный недостаток остается каким был. Хоть v7 - все равно в голову они влазят. Четвертое и пятое - остается в силе для любых версий UUID. Как избежать третьего в контейнерах с одинаковой энтропией, даже для v7 - не понимаю. Да, с дублированием я встречался буквально разы, но все же встречался. И стандарт v7, помогая решить проблему индексации в БД, вероятность дублирования только повышает.

Да банально если ты используешь uuid ты лишен бинарного поиска по идентификатору, а в больших таблицах это ощутимо

Зачем вам бинарный поиск, почему вы его вдруг лишились (а он был?) и почему именно он ощутим в больших таблицах?

Конкретно у себя на работе его не пользую, да и вряд ли кто то прям им пользуется, но вроде оптимизатор запросов в БД под капотом им пользуется, но это не точно)

В БД оптимизатор или идет по индексу, обычно, BTree, или строит хеш-таблицу.

И UUID до v7 влияло не столько на поиск в индексе, сколько на перестроение этого индекса при добавлении новых записей. Так как намного дешевле перестраивать индекс при добавлении монотонно возрастающих значений, чем случайных.

Понял, спасибо!

ещё наверное не получится от ротировать по uuid как например по id чтобы получить последние записи

:-) У вас в базе максимум миллиард объектов и тысяча миллиардов записей действий с ними.... А уж индексов их включающих... Скажем так "много".
Я не говорю даже о объеме информации хранения (она разная особенно для UTF8 VARCHAR). А вот скорость работы (и объем) с индексами по NUMBER и VARCHAR полям отличается значительно. Причем "специальный" UUID data type на индексах производительностью совсем "не блещет"...

Но мы же говорим не про UTF8 VARCHAR, а про хранение 130 бит информации. А это всего в два раза больше чем bigint...

:-) Теоретизировать - это хорошо... Вы на практике в БД попробуйте и сравните.... :-)

:-)
Oracle - Number having precision p and scale s. The precision p can range from 1 to 38. The scale s can range from -84 to 127. Both precision and scale are in decimal digits. A NUMBER value requires from 1 to 22 bytes.
PostgeSQL - bigserial 8 bytes large autoincrementing integer1 to 9223372036854775807

Насколько хорошо индексирует ваша СУБД UUID?

По факту приведены одни и те же вопросы с разных сторон просто. "Нет необходимости в центральном мастере", "Лучше для распределенных систем" и "Подходит для автономной генерации данных" - это одно и тоже.

"UUID не привязаны к конкретной технологии баз данных или производителю" - ну это как сказать, где-то есть тип данных UUID, где-то придется строкой хранить. Опять же как затратно индексируется все это. Будет ли на разных СУБД одинаково? С числами таких проблем не будет, думаю

"Везде уникально" и "Отсутствие предсказуемости" - зависит от умения готовить UUID. Опять же отсутствие предсказуемости вредит индексации.

отсутствие предсказуемости вредит индексации

Это вроде как решили в UUIDv7

Есть такие минусы.

Операции сортировки и between и им подобные утрачивают смысл.

В операции join операции с целым числом эффективнее чем со строкой. Можно найти примеры где измеряют производительность. На большом объёме разница существенна.

Они могут использоваться в различных системах баз данных

Вот пример из моего личного опыта — в Manticore нельзя, максимум 64 бита

В ElasticSearch/OpenSearch тоже. Но там текст бахать, это всё-таки обычно не основная БД

Разным задачам разные ID.

Если ID для обмена данными с внешними системами - да UUID обязателен.
Если ID для обращения к записи из веб-клиента - да UUID обязателен.

Если для RI в рамках базы - UUID избыточен и сложен для траблшутинга. INT проще запомнить когда ковыряешься в связях.
Если для работы с базой в коде - UUID избыточен и сложнее в работе чем INT.

А производительность (MSSQL) по сравнению с INT - это disaster.

почему uuid обязателен для апмшки и внешн. систем? Логический ключ, если он есть, обычно, лучше

Логический ключ, если он есть, обычно, лучше

Я не совсем в курсе, что есть "логический ключ", но если комплексный ключ из полей записи, то могут подвернуться изменяемые данные. Постоянные ID лучше.

да, зависит от того, могут ли подвергаться изменению.

Например, NFT токен как логический ключ имеет id внутри контракта и сам контракт (адрес и сеть) (tokenId:uint256,(chainId:int,address:20bytes)). Ничего не может измениться, введение суррогатного ид только запутает.

А вот с человеком/профилем, например, такое не выйдет, да. Впрочем, нет никакого кошмара передать int64 как id в апи. Telegram ещё не умер от такого. Меньше новых сущностей не имеющих смысловой нагрузки - меньше путаницы

Если ID для обмена данными с внешними системами - да UUID обязателен.Если ID для обращения к записи из веб-клиента - да UUID обязателен.

На самом деле нет.

Ну узнает весь мир ваши внутренние ID всяких сущностей. И что? У вас же все закрыто нормальными правами и перебор чего-то интересного невозможен? Верно?

Часто встречаемое исключение когда надо что-то ограниченного доступа сделать через ссылку. Но при этом если эта информация утечет в целом ничего страшного. Местоположение вашего курьера на карте, например. Ссылка в СМС, получателю про которого мы ничего не знаем. Или трекинг номер посылки. Прямо открывать всё всем не хочется. Но утечет и ладно, потерь никаких.

Ну узнает весь мир ваши внутренние ID всяких сущностей. И что?

Чтение данных методом перебора ID.
Сам так делал, и не раз.
Да, и сейчас синхронно начитываем крупную базу, а пришлось бы ручками.
А разрабам запрещаю это непотребство в нашей работе. :)

Я буквально чуть ниже про это написал. Вы точно прочитали полностью мое сообщение?

Вы спросили, я ответил.
Даже если вы сами ответили на вопрос - вы спросили, и я посчитал нужным ответить. :)

если запрос авторизован, то проблем 0. Получит 401/403.

Если запрос открытый - да пусть перебирает на здоровье, паблик апи же. Прикрутить капчу v3 в конце концов.

Если всякие одноразовые ссылки - тогда уж да. Но сколько сущностей в приложении оно распространяется? В большинстве случаев 0, в ином случае мизерная часть энивэй.

Если ID для обмена данными с внешними системами — да UUID обязателен.
Если ID для обращения к записи из веб‑клиента — да UUID обязателен.

Можно в отдельную колонку положить "внешний ID" и генерировать его в виде рандомной строки, пример. Вот здесь варианты привёл: https://habr.com/ru/articles/760272/comments/#comment_25975708

Т.е. ИМХО если ID не генерируется извне системы, то лучше использовать что-то своё, типа рандомной строки с префиксом. Если же ID генерируется (условно) на фронтенде - то да, UUID лучше т.к. оно лучше стандартизировано и условный фронтендерский фреймворк лучше и быстрее сгенерирует то что нужно.

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

Вообще-то, нет. Часто встречаю, что в последних двух или трех цифрах закодирован источник автоматически увеличивающегося идентификатора. Соответственно, инкремент происходит с шагом 100 или 1000.

И, кстати, один источник автоматически увеличивающихся идентификаторов может использоваться для нескольких таблиц. И не всегда одной базы данных.

Интересно, почему последних, а не первых?

Потому что разницу между 1000000000123 и 10000000000123 вы заметите не сразу а разницу между 123001 и 123010 - мгновенно.

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

Инкремент не налезет на код в таком случае.

Работал как-то для крупного банка. Так у них на одного сотрудника 5(!) этих ваших UUID. А чё, -- халява! Доходило до того, что приходилось UUID'ы связывать через e-mal'ы.

Это немного не из этой области. С автоинкрементом может быть (и часто бывает) такая же ситуация.

Странно выглядит статья про UUID без ссылки на новый стандарт UUID, который уже на последней стадии утверждения. Введенные новым стандартом UUID седьмой версии (UUIDv7) полностью устраняют очень серьезные недостатки прежних версий UUID. В первую очередь, максимально увеличивается скорость индексирования и вставки новых записей, а также поиска записей в БД. Более полно достоинства UUIDv7 изложены в статье "Встречайте UUID нового поколения для ключей высоконагруженных систем" и многочисленных комментариях к ней.

Автор комментируемой статьи безусловно прав, отмечая достоинства UUID. Но к ним необходимо добавить следующее:
1. Неотъемлемые недостатки автоинкремента (относительно указанных в статье достоинств UUID) заставляют проектировщиков БД использовать составные бизнес-ключи, которые многократно длиннее, чем UUID, сильно замедляют соединение таблиц и порождают большое количество трудноуловимых и трудноустранимых дефектов данных. Кроме того, составные ключи не подходят для правильно спроектированных больших хранилищ данных (по методологиям Data Vault или Anchor Modeling).
2. Автоинкремент, в отличие от UUID, не подходит для интеграции данных из разных информационных систем и для поиска причин дефектов данных, так как он требует замены ключа при передаче данных из одной системы в другую.
3. В условиях отсутствия подробной документации (а согласно Манифесту господствующей ныне идеологии Agile, решение проблемы клиента важнее проработанной до мелочей документации), отсутствия метаданных и констрейнтов (их не модно создавать, как и комментарии в программном коде) системные аналитики вынуждены изучать фактическую структуру БД по значениям в ключевых полях. При этом UUID дают абсолютную уверенность в выводах, а вот автоинкремент порождает массовые случайные совпадения ключей.

Поэтому "экономия на спичках" в виде выбора автоинкремента для сокращения длины идентификатора оборачивается огромными финансовыми потерями, хотя, конечно, выглядит как полезная работа.

Автоинкремент ... требует замены ключа при передаче данных из одной системы в другую.

Хм, а зачем?

Мы как-то без замены передаем и ничего, работает...

Сказали, что надо)))

иначе внешняя система узнает внутренний ид иииии... произойдет ужас по мнению автора

Поэтому "экономия на спичках" в виде выбора автоинкремента

Это экономия сотен и тысяч часов дебага. Когда очередной разработчик на глаз перепутал пару UUID и сделал что-то не то.

ахах, жиза.

Я обычно просто забываю чё искали при переключении окна. Но это ладно, их еще (-) и экранировать при грепании надо

Какой-то странный довод для аргументации архитектуры системы/хранилища. Может разработчик всё-таки не будет что-то там делать "на глаз", "на память" и т.д. и т.п. Или может он просто хреновый разработчик?

В идеальном мире не будет. В реальном мире копаются в данных и что-то там ищут/меняют регулярно.

не понял, чем «перепутать пару uuid» принципиально отличается от «перепутать пару числовых id»

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

В этом плане я наоборот вижу у UUID преимущества:

  • Если переписывать UUID руками, то при опечатке с высокой вероятностью получится несуществующий id, а значит ничего страшного не случится (а при опечатке в числовом инкрементном id можно, например, случайно удалить не тот объект)

  • Длинность UUID заставляет его не переписывать, а по возможности копировать, и при копировании нетрудно проверить, что копирование выполняется из правильного места

  • Если случайно скопировать id из другой сущности, то при попытке применить его к нужной сущности этот id опять же окажется скорее всего несуществующим (в то время как условный инкрементый id=1 будет существовать у всех сущностей, и это опять же создаёт риск случайно удалить не то)

(Впрочем, всё то же самое применимо и к Snowflake/Sonyflake, только вероятности немножко другие)

по тому-что в uuid ты запоминаешь только 3 последний символа, абсолютно не понятен порядок значения. Это просто огромный кусок безсвязных символов. Я понимаю, что у всех никогда такого не бывает, да, все всегда целиком вырезают, все всегда в логах все сразу грепают качественно, с экранированием этих - , ага.

Вот, кстати, что мешает базам данных иметь опцию о формате отображения UUID'ов? Ну или клиентам к БД, типа DBeaver. Хочешь - будет отображаться как e027e693-4525-45bb-9fad-319e7373b839, хочешь - в base32 01H7K17QKJ5ND32CQS0NB03FXW .

Чтобы еще сильнее увеличить путаницу?

Люди обычно смотрят на id не потому что они красивые, а чтобы их скопировать, куда-нибудь вставить и что-нибудь по ним грепнуть. И чем этот процесс однозначнее тем лучше.

это же не уровень, как-раз уровень клиентов.

Мешает ничего, но формат uuid по некой причине стандартизирован так, как стандартизирован.

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

Мы как-то на наших задачах смотрели - uuid был в два раза медленнее на селектах. Ну оно и понятно - он в два раза больше, а в обычной такой OLTP-базе у тебя половина колонок - это id (ведь кроме PK есть куча FK). А размер строки - влияет буквально на все.

Т.к. БД обычно - узкое место, как-то стрёмно прям 2х терять от входа.

Снаружи системы - во внешних API и кафках, все равно обычно мешанина - где guid, где long. Для людей вообще email могут быть. И мы все равно об отдельную табличку маппим с внешних id (которые для универсальности вообще varchar) на внутренние.

Сейчас погуглил быстро - особо не нашел хороших статей с тестами на производительность, но примерно такие результаты гуглятся.

В общем пока пихать uuid везде стремно, хотя плюсы понятны и желанны.

Дождитесь официальной реализации UUIDv7 для Вашей СУБД. Для PostgreSQL ждать осталось недолго. Она обеспечит производительность почти как у автоинкремента. Сейчас уже есть самодельные реализации UUIDv7, но они хуже, так как не обеспечивают всех преимуществ, предусмотренных новым стандартом. Если торопитесь, то можете их попробовать.

Проблему периодического получения новых записей из таблиц UUID никакой версии не решает. Все равно приходится использовать bigserial.

Я понимаю, о чём вы, но забавно читать про генерацию средствами PostgreSQL, в то время как основная фича UUID'ов - это всё-таки генерация вне БД, ну или до обращения к ней.

Проект стандарта оставляет выбор места генерации UUID за проектировщиком информационной системы, отмечая более высокую производительность поиска записей при генерации в БД, чем при генерации на клиенте: Applications using a monolithic database may find using database-generated UUIDs (as opposed to client-generate UUIDs) provides the best UUID monotonicity.

Нужно добавить, что производительность создания записей при генерации UUID на сервере приложения низкая, и следует предпочесть генерацию UUID в БД.

Но проект стандарта разрешает и распределенную генерацию UUID.

Кроме того, проект стандарта предусматривает комбинированный вариант, когда в записи сохраняются оба идентификатора (сгенеренный на клиенте и сгенеренный в БД), а идентификатор, сгенеренный на клиенте, используется для обеспечения ссылочной целостности информационной системы и для идентификации ответов сервера на клиенте: In addition to UUIDs, additional identifiers MAY be used to ensure integrity and feedback. Хотя, конечно, эта фраза не очень четко сформулирована.

У нас был ИД UUID, пришлось сделать ещё одно поле инкрементный int чтоб можно было выбирать первые 1000 строк, с UUID такого не получится - никакой сортировки, при добавлении записи новая строка залезет внутрь первых 1000 строк - куда не надо

Хехе, краткий вывод практиков из комментариев (и я к ним присоединяюсь): не используйте UUID, кроме случаев, где без UUID по какой-то причине нельзя.

  • UUIDv4 плохо дружат с индексами многих БД. В смысле индексы от них толстеют и начинают тормозить - B-деревья не любят случайные данные.

  • UUID весит в 2 раза больше BIGINT и если используется как первичный ключ, то все индексы станут жирнее (потому что все индексы включают в себя копию первичного ключа). Жирные индексы не влезают в кеши и тормозят.

  • Из-за плохой читаемости UUID, тяжело отлаживать систему.

UUIDv4 плохо дружат с индексами многих БД

ну помимо v4 есть другие варианты, решающие эту проблему. включая проект нового rfc (и всякие ulid)


UUID весит в 2 раза больше BIGINT и если используется как первичный ключ, то все индексы станут жирнее (потому что все индексы включают в себя копию первичного ключа)

разве? ЕМНИП как минимум в постгресе это не так, в индексах хранятся указатели на внутренние идентификаторы строк таблицы.

snowflake id или sonyflake id почти всегда лучше, ибо 8 байт и монотонность. Возможно, хуже там где "Отсутствие предсказуемости" - это серьезный фактор. Ну или у вас ну ооочень много юников.

snowflake id и sonyflake id хороши в простом и идеальном мире. Разработчики UUIDv7 внимательнейшим образом изучили и snowflake id, и sonyflake id. Но в условиях царящего в IT бардака (включая совпадения MAC-адресов) UUIDv7 - это единственный абсолютно надежный вариант. Он позволяет спокойно проектировать и интегрировать сколь угодно сложные критические высоконагруженные информационные системы, не обкладываясь со всех сторон "страховочными механизмами"

"спокойно" - если у вас в 4 раза больше денег на сервера

Ненадежные короткие ID на самом деле вынуждают усложнять структуру данных и городить  "страховочные механизмы", которые требуют в 10 раз больше денег на сервера. Нужно взглянуть чуть шире. Экономия в одном месте оборачивается огромными расходами в другом

1 размер ключей и индексов неоправдано разпухнет

2 базы mssql для pk строит кластерные индексы, и при вставке в середину, перестраивает его, а это постояная работа с диском

3 если отображение должно быть в порядке добавления, то придется добавлять дату, потому что order by id невозможен

2 решает упорядочненный uuid.


1 пункт не так страшен, если не перебарщивать с нормализацией.

решает упорядочненный uuid

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

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

Идеальной упорядоченности мне и не нужно. Мне нужно, чтобы BTree меньше перестраивалось. А для этих целей, если сначала прилетит запись с ID на 10 больше, а потом на 10 меньше - всё в одну и ту же страницу листов дерева попадет с очень высокой вероятностью.

да и не нужен идеально упорядочненный, от того, что, условно, запись 998 прилетит после 999 и 1000, ничего страшного не случится

Для bigserial - именно так. А для UUID - совсем не так, так как с разных потоков будет лететь с разными псевдослучайными сегментами. Что и приведет к большим издержками при перестраивании BTree.

Просто представьте, что вы работаете в поддержке и вам пользователь выставляет тикет со скриншотом проблемного документа. Сравните затраты на перебивание числового 10 или даже 8 значного номера по сравнению с 16 или 32 шестнадцатеричнвми числами.

Что важно, эта проблема с UUID проявляется уже при первых сотнях записей в таблице, а не когда у вас там 10млрд строк.

"Программисты - это люди, которые решают непонятные вам проблемы непонятными вам способами"

пользователь выставляет тикет со скриншотом проблемного документа

dpScreenOCR не пробовали?.

Не пробовал и не был в курсе существования, как и коллеги из разных компаний, с которыми взаимодействовал. При попытке запустить установщик, на него ругается win defender.

Мое утверждение простое: перечисленные плюсы UUID начинают работать на больших объемах и высоких нагрузках, до которых 99.9 таблиц в БД обычно никогда не дотягивают.

А вот крайне существенный недостаток начинает портить жизнь немедленно и заключается он в том что UUID обычный человек не может в своей голове запомнить и сравнить с другим. Помогает только копипаст и формулы в excel.

Мое утверждение простое: перечисленные плюсы UUID начинают работать на больших объемах и высоких нагрузках, до которых 99.9 таблиц в БД обычно никогда не дотягивают

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


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

При попытке запустить установщик, на него ругается win defender.

Чем OpenSource хорош, что если паранойя - всегда можно собрать самому из исходников https://github.com/danpla/dpscreenocr предварительно изучив их.

Мое утверждение простое

Мое еще проще. UUID нужен только тогда, когда без него нельзя или очень сложно обойтись.

А dpScreenOCR решает проблему не UUID, а скриншотов, которые обожают присылать пользователи.

UUID нужен только тогда, когда без него нельзя или очень сложно обойтись.

Вот, рад что в главном мы сходимся!

Не надо безоглядно делать UUID во всех таблицах только потому что "у них есть 7 преимуществ".

Это нишевое решение под нишевые задачи.

ровно то же самое можно сказать про числовые id )

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории