для чего нужен uuid

UUID и браузеры. Почему фронтенд живет без страшных айдишников?

Решил я делать свой пет-проект по учету прочитанных книг на PWA. Покорять новые технологии и все такое. Расчет был на то, что с его выложу и установлю на телефон и вот у меня есть мобильное приложение, которое можно использовать оффлайн. Хочу я сгенерировать UUID, чтобы сохранить книгу, а не нахожу API. Предлагаю разобраться почему.

Что такое UUID

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

UUID представляет собой 16-байтное число в HEX’е формате:

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

Способы генерации UUID

для чего нужен uuid

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

1 и 2 версии использовали время с точностью до 0.1 микросекунды + MAC адрес, что гарантировало практически полное отсутствие возможности получить дубликат. Чтобы полностью добить эту вероятность первая версия добавляет рандомную соль, а вторая ничего не делает (вторую версию мы не любим, она вообще может сгенерировать только 64 уникальных id за семь минут).

3 и 5 хешируют пространство имен (Url, FQDN, OID) + само имя. Таким образом в каждый момент времени мы получаем абсолютно идентичные UUID для одних и тех же входных параметров.

Отличие 3 и 5 версии только в том, что 3 использует для хеширования MD-5, а 5 — SHA-1.

4 же версия просто использует рандом ¯_(ツ)_/¯.

Почему его нет в браузере

JS не имеет доступа к данным машины

Мы не можем получить MAC-адрес пользователя, мы не можем получить данные его IP, а так же вообще что-либо с его машины без разрешения пользователя.
Да, мы можем загружать файлы и делать красивые file-инпуты на фронте, но мы можем получить только конкретный файл, который нам предоставит пользователь. Но согласитесь, как бы не шибко удобно запрашивать на каждый UUID по файлу. Неудобно их запрашивать даже каждый раз при входе на сайт.
Сделано же это из благих целей: представьте, что читаете вы Хабр, а тут:

для чего нужен uuid

И больше никаких проблем с высшим образованием.

Потому что до недавних пор он был просто не нужен

Браузер для того, чтобы сидеть в интернете.
Через браузер мы заходим на сайт. Если мы зашли на сайт — нам отдали страничку. А раз нам ее отдали — значит мы связаны с сетевым узлом который может сгенерировать UUID и сами мы можем этого не делать. По факту, нам как фронту вообще на ID информации все равно, мы отдали, а дальше это уже проблема принимающей стороны.

Вы можете возразить, что есть PWA, и что оно есть аж с 2007 года. Но так уж вышло, что PWA никому не нужен, примерно, с того же самого времени. (Хотя нынче Play Market позволяет загружать PWA как приложения, но. ). Сами посудите, много вы PWA приложений установили? Я даже Хабр не поставил.

Но осадочек остался.

Какие трудности вас ждут

Точность времени

Я бы не стал называть это большой проблемой.

Мы можем получить время с точностью только до миллисекунды, в то время как первая версия UUID делала это с точностью до 100 наносекунд.

Ну чисто теоретически мы можем получить и с точностью до 1 микросекунды, но это будет время от открытия вкладки (это если мы сейчас про performance.now() ), что уже не так заманчиво.

Идентификация браузера

Браузеры вообще не уникальны и сейчас я вам это докажу.

Для идентификации клиента HTML Living Standard нам предлагает использовать The Navigator object.

А теперь внимание сравним то, что нам предлагают сравнивать

БраузерappCodeNameappNameplatform​productproductSubvendorvendorSub
ChromeMozillaNetscapeWin32Gecko20030107Google Inc.
Mozilla 75MozillaNetscapeWin32Gecko20100101
Mozilla 45MozillaNetscapeWin32Gecko20100101
Internet ExplorerMozillaNetscapeWin32Gecko
Microsoft EdgeMozillaNetscapeWin32Gecko20030107Google Inc.

Как вам такое? Почувствовали все разнообразие клиентов? Вот и я нет.

Но надо признать, что местами отличаются userAgent и appVersion :

Тут Edge впереди планеты всей, так как он отображает IP, и мы можем использовать его. Но это только в Edge. А так, как видите, многого с навигатором не навоюешь.

Как это реализовал я

для чего нужен uuid

Для себя я решил отталкиваться от своих нужд и особенностей архитектуры своего приложения.

Последние 6 байт я беру из SHA-1 хеша логина — можно идентифицировать 281,474,976,710,656 уникальных пользователей (если взять расчет на то, что не будет коллизий). Тоже с запасом (у меня их всего 30).

1 байт у нас отводится на версию (M) и вариант (N).

Оставшиеся 3 байта я солю рандомом.

Если вдруг мое приложение станет супер-пупер популярным и 100,000 и они будут за минуту каждый делать по 100 книг, то за миллисекунду будет генерироваться:

$$
100,000 * 100 / 60,000 = 166
$$

Вероятность того, что совпадут два:

Это очень мало и этого мне хватает

Реализацию можно посмотреть тут.

Предвещая вопрос «А почему же не рандом?»

Да, есть такой легендарный код

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

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

В случае же с рандомом — данные будут вставляться в табличку куда ни попадя.

Источник

Про uuid-ы, первичные ключи и базы данных

для чего нужен uuid

Статья посвящена альтернативным версиям Qt-драйверов для работы с базами данных. По большому счету отличий от нативных Qt-драйверов не так много, всего пара: 1) Поддержка типа UUID; 2) Работа с сущностью «Транзакция» как с самостоятельным объектом. Но эти отличия привели к существенному пересмотру кодовой реализации исходных Qt-решений и изменили подход к написанию рабочего кода.

Первичный ключ: UUID или Integer?

Впервые с идеей использовать UUID в качестве первичного ключа я познакомился в 2003 году, работая в команде дельфистов. Мы разрабатывали программу для автоматизации технологических процессов на производстве. СУБД в проекте отводилась существенная роль. На тот момент это была FireBird версии 1.5. По мере усложнения проекта появились трудности с использованием целочисленных идентификаторов в качестве первичных ключей. Опишу пару сложностей:

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

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

Использование UUID-ов в качестве первичных ключей сводило к минимуму архитектурную проблему, и полностью решало программную. UUID-ключ генерировался перед началом вставки записи на стороне программы, а не в недрах сервера БД, таким образом дополнительный SELECT-запрос стал не нужен, и требование единой транзакции утратило актуальность. FireBird версии 1.5 не имел нативной поддержки UUID-ов, поэтому использовались строковые поля длинной в 32 символа (дефисы из UUID-ов удалялись). Факт использования строковых полей в качестве первичных ключей нисколько не смущал, нам не терпелось опробовать новый подход при работе с данными.

У UUID-ов есть свои минусы: 1) Существенный объем; 2) Более низкая скорость работы по сравнению с целочисленными идентификаторами. В рамках проекта достоинства оказались более значимы, чем указанные недостатки. В целом, опыт оказался положительным, поэтому в последующих решениях при создании реляционных связей предпочтение отдавалось именно UUID-ам.

Примечание: Более подробный анализ UUID vs Integer для СУБД MS SQL можно посмотреть в статье «Первичный ключ – GUID или автоинкремент?»

Первый драйвер для FireBird

В 2012 году мне снова довелось поработать с FireBird. Нужно было создать небольшую программу по анализу данных. Разработка велась с использованием QtFramework. Примерно в это же время у FireBird вышла версия 2.5 с нативной поддержкой UUID-ов. Я подумал: «Почему бы не добавить в Qt-драйвер для FireBird поддержку типа QUuid?» Так появилась первая версия Qt-драйвера с поддержкой UUID-ов. Этот вариант не сильно отличался от оригинальной версии драйвера и, в основном, был ориентирован на использование в однопоточных приложениях.

Появление сущности «Транзакция»

Следующая модификация Qt-драйвера для FireBird произошла в конце 2018 года. Наша фирма взялась за разработку проекта по анализу данных большого объема. Для фирмы выросшей из стартап-а эта работа была очень важна, как с финансовой, так и с репутацио́нной точек зрения. Сроки исполнения были весьма жесткие. В качестве СУБД была выбрана FireBird, несмотря на определенные сомнения в ее пригодности. Хорошим вариантом могла бы стать PostgreSQL, но у нашей команды на тот момент отсутствовал опыт эксплуатации данной СУБД.

Новая концепция не нуждалась в сущности «транзакция», как в самостоятельной единице, тем не менее, я не стал ее упразднять. Дальнейшая эксплуатация показала, что наличие объекта «транзакция» делает работу с базой данных более гибкой, дает больше инвариантов при написании кода. Например, разработчик может передать объект «Транзакция» в функцию в качестве параметра, явно говоря таким образом, что внутри нужно работать в контексте указанной транзакции. В функции можно проверить активна транзакция или нет, можно выполнить COMMIT или ROLLBACK. Для вспомогательных операций можно создавать альтернативную транзакцию, не затрагивающую основную. Таких возможностей нет у нативных Qt-драйверов.

Ниже приведен пример с тремя функциями, которые последовательно вызываю друг друга. Каждая функция запрашивает объект подключения к базе данных (Driver) у пула коннектов. Так как функции вызываются в одном потоке, они получают объект коннекта, ссылающийся на одно и тоже подключение к БД. Далее в каждой функции создается собственный независимый объект транзакции и все последующие запросы будут выполняются в его контексте.

Приведенный пример не будет работать с нативным Qt-драйвером, причина описана выше: ограничение на одно подключение и одну транзакцию

В примере экземпляры транзакций (1-3) созданы для наглядности. В рабочем коде их можно опустить. В этом случае транзакции будут создаваться неявно внутри объекта QSqlQuery. Неявные транзакции всегда завершаются ROLLBACK-ом для SELECT-запросов и попыткой COMMIT-а для всех остальных.

Ниже показано как можно использовать одну транзакцию для трех sql-запросов. Подтвердить или откатить транзакцию можно в любой из трех функций.

Драйвер для PostgreSQL

Драйвер для MS SQL

Чего нет в классе Driver

Описываемые здесь драйверы не повторяют один в один функционал Qt-решений. В классе оставлены следующие методы:

С введением сущности «Транзакция» они утратили актуальность и нужны исключительно для отладки и диагностирования их вызовов из Qt-компонентов.

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

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

Новые функции

Лицензионные ограничения

Зависимости

В реализации драйверов используется система логирования ALog, которая является составной частью библиотеки общего назначения SharedTools.

Демо-примеры

Специально для этой статьи был создан демонстрационный проект. Он содержит примеры работы с тремя СУБД: FireBird, PostgreSQL, MS SQL. Репозиторий с драйверами расположен здесь, он подключен в проект как субмодуль. Библиотека SharedTools так же подключена как субмодуль.

Проект создан с использованием QtCreator, сборочная система QBS. Есть четыре сборочных сценария:

Драйвера в первую очередь разрабатывались для работы в Linux, поэтому эксплуатационное тестирование выполнялось именно для этой ОС. В Windows будет работать FireBird-драйвер (проверено), для остальных драйверов тестирование не проводилось.

Демо-примеры записывают следующие логи:

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

Заключение

Черновой вариант статьи не предполагал наличие этого раздела, за что старый товарищ и, по совместительству, корректор подверг меня критике: «Мол, непонятна мотивация, целеполагание неясно. Зачем ты вообще писал эту статью?!» Что ж, исправляюсь!

Источник

Двигаем биты — или как реализовать свой стандарт UUID

для чего нужен uuid

Я работаю над открытой реализацией предложенного стандарта идентификаторов UUIDv7. На данный момент спецификация существует в виде IETF черновика. Черновик уже пережил два переиздания, и мы постоянно обновляем спецификации. Но сам документ — это дело простое. Для того чтобы кто-то мог воспользоваться новыми UUIDv7, нам надо написать как можно больше открытых имплементаций на различных языках.

Мне выпала стезя писать клиент на Golang. И всё бы было достаточно просто, если бы не сам стандарт. Для создания UUIDv7 вам нужно будет постоянно двигать различные биты в разных направлениях.

В этой статье я расскажу, с чем столкнулся, помогая с разработкой на golang.

Работа над подобным проектом — эта та ещё веселуха. Черновик стандарта находится в разработке, и после того как мы пробуем сделать что-то одно, идём, пробуем и разбираемся, что же получилось в итоге.

Во-первых, если вы посмотрите на обсуждение в github, то читать придётся очень много. Каждый из участников пытается спокойно обосновать свою точку зрения, это спокойствие обычно заключается в том, что любой комментарий занимает от двух до пяти страниц текста. Все эти комментарии сопровождаются вдумчивым вступлением и очень осторожным объяснением того, почему автор предыдущего комментария — дурак.

Во-вторых, надо быть готовым воевать. Над стандартом сейчас активно работают три человека. Я подключился к процессу имплементации. И всё бы хорошо… Открываешь документ, начинаешь писать код, аккуратно выравниваешь все значения в памяти, коммитишь код, идёшь, проверяешь комментарии… И впадаешь в ступор. Там уже дерутся по поводу того, сколько бит должна занимать та-самая-фича, которую ты только что писал. Тяжело вздыхаешь и идёшь переписывать уже существующий код.

Иногда, после того как я писал код, ко мне приходили и спрашивали моего мнения. «Удобно ли тебе писалось?». Я наивно отвечал: «Да не очень, но ничего, написалось». В таком случае мне радостно отвечали: «Отлично! Мы пересмотрим стандарт в таком случае, чтобы сделать его проще!». Фейспалм.

Посему основными требованиями для проекта были:

Для начала быстрое введение в тему UUIDv7

Вместо генерирования случайных значений в идентификаторах — мы будем зашивать дату и время в сам идентификатор. Разница между UUIDv4 и v7 заключается в том, что последний является бинарно-сортируемым.

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

Итак, давайте посмотрим на то, с чем мы работаем:

для чего нужен uuid

Вот наш герой. Первые 36 бит занимает unix-timestamp. Само по себе это уже гарантирует бинарную совместимость. Мы решили сократить 64 бита ts до 36 бит. При этом мы сможем сохранять даты до 4147 года. Оставшиеся биты нам понадобятся для хранения субсекундной точности. В UUID нам важно сохранить как можно больше энтропии. Первые 28 бит полного unix-ts хранят в себе нули на следующие два тысячелетия, посему мы их отсеиваем.

Далее у нас есть два замечательных прикола, называющиеся «обратной совместимостью». Нам надо хранить значения полей ver и var для совместимости с UUIDv4.

Итак, как вы видите, ничего не равняется ни по каким границам байтов. Более того, значение в поле subsec разбито на две части полем ver. Жизнь была бы намного проще, если бы мы могли просто выровнять всё по байтам и сложить их вместе. Но увы. Придётся выкручиваться.

Давайте посмотрим, что же такое UUID?

Какую бы версию UUID мы ни реализовывали, в конечном счёте нам надо выдать 16 байт. А так как нам нужно что-то, что очень легко портируется, переносится и адаптируется, то было бы глупо хранить UUIDv7 в любом другом формате, кроме как массив байт.

В файле uuid_base.go мы описываем этот массив и создаём простые форматтеры, позволяющие выводить сам идентификатор в нескольких широко используемых форматах.

Разврат начинается, когда мы добираемся до самой процедуры генерации идентификатора. В файле uuid_v7.go вы можете найти функцию Next, которая, собственно говоря, и генерирует идентификатор. В текущей реализации пользователь может установить определённые параметры генерации, поэтому сами поля заполняются по-разному.

Если вы посмотрите на эту логику, то увидите, что нам постоянно нужно выставлять определённые биты в определённых позициях в этом массиве данных.

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

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

Так как в будущем этот код будет использоваться для генерации UUID версий 6, 7 и 8, то мы пытаемся повторно использовать базовый код. Посему, так как мы не можем напрямую приводить тип UUIDv7 к массиву бит, нам придётся создать функцию, которая будет обращаться к аналогичной функции базового класса.

А после добро пожаловать в мир очень простой магии.

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

Создаём байт, равный единице. В бинарном представлении у нас получается 0000 0001. Сдвигаем выставленный бит в позицию, которую нам надо получить, и делаем OR. Сравниваем результат с нулём, и у нас в руках нужный бит.

Описанные выше операции тривиальны и не занимают много времени на процессоре, экономя память.

Для выставления бита возьмём за базу тот же код:

Для того чтобы выставить бит, мы опять создаём байт с одним битом и сдвигаем выставленный бит налево, после чего делаем побитовое OR, что гарантирует результат с поднятым битом.

Для того чтобы этот бит опустить, мы делаем побитовое AND с NOT нашего числа.

Итого: мы можем без особых проблем манипулировать битами в памяти напрямую.

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

Для начала в данном проекте нам потребуется пара индексаторов:

По факту длина нашего UUID равна 123 битам, а не 128. Пять бит уходят на поля var и ver. Поэтому, когда мы складываем биты вместе, нам нужно знать их «относительную позицию» от начала нашего идентификатора.

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

Так как мы пользуемся нашими функциями для индексирования, то поля var и ver останутся незаполненными. Их надо будет забить статическими значениями после того, как генерация была завершена.

Со всем вышеописанным работа с битами в байтах напрямую становится проще.

Мы просто генерируем куски нашего идентификатора и собираем их в памяти с помощью одной функции.

Эта функция получает и возвращает значение u.currentPosition, которое позволяет следить за тем, куда мы пишем в данный момент.

Страшная работа по написанию битов в байты выглядит не так уж страшно.

Берём всё, упаковываем, тестируем, создаём релиз, отдаём ребятам «на попробовать». И после сидим и ждём, что будет дальше, и что мы будем менять. Так как переписывать придётся всё, то надо перестать париться о том, что какой-то код можно будет спасти, или о том, что какой-то код можно будет использовать ещё раз.

При этом мы используем 16 байт памяти и не двигаем наш конечный массив туда-сюда по памяти. Экономим память один к шестидесяти четырём, так как массив из 128 бит хранит каждый бит в одном байте из-за выравнивания, и конечный объём памяти на хранение одного идентификатора (128*8) будет равняться одному килобайту, что расточительно для того, что занимает 16 байт.

Весь код доступен по лицензии MIT, посему, если вам приходится работать с битами в golang, вы запросто можете копировать функции из bitsetter.go. Этот код может работать с массивами произвольной длины.

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

НЛО прилетело и оставило здесь промокоды для читателей нашего блога:

Источник

Что такое UUID?

Например при переходе по url posts/1 мы получим саму статью. А с использованием uuid идентификатор поста будет что-то вроде этого posts/ac5fb2c6-e43a-48e3-a116-47fc719a69c5

для чего нужен uuid

для чего нужен uuid

2 ответа 2

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

Например, захотел я собрать все посты с другого сайта, пишу:

И через пару секунд получаю содержимое 1000 постов с того сайта.

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

Например у вас работают две копии сайта. В каждом из них идет автоинкрементное добавление id++. И вот вдруг к вам поступила задача слить эти две базы в одну. Вы столкнетесь с тем, что и в одной и в другой базе есть одинаковые id. А вот с uuid таких проблем не возникнет

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

Источник


Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *