id integer vs uuid
От: vsb Казахстан  
Дата: 19.08.22 14:27
Оценка:
Наверное набивший оскомину вопрос, извините.

У каждой таблицы с данными должен быть синтетический ключ id. Ссылки на эту таблицу должны быть по этому ключу.

Два основных подхода:

1. Использовать целочисленный ключ (обычно 1, 2, 4, 8 байтов в зависимости от предполагаемого числа строк), который генерируется в виде глобально возрастающего значения.

2. Использовать 128-битный UUID.

2.1. Генерировать UUID случайным образом.

2.2. Генерировать UUID в виде локально возрастающей последовательности (она возрастает, но может в какой-то момент "переполняться").

2.3. Генерировать UUID в виде глобально возрастающей последовательности (для простоты можно считать, что левые 64 бита это timestamp, правые 64 бита случайны).

На что это влияет:

1. Чем меньше байтов, тем быстрей оно работает. Ну это, наверное, очевидно. И читать меньше и памяти меньше занимает и кешей меньше требует и индексы меньше, и промахов меньше. Разница между типичным 4-байтовым целым и 16-байтовым UUID в 4 раза. Понятно, что упереться в производительность в этом месте — надо постараться, с другой стороны вопрос про то, что будет в каждой таблице, так что какая-то доля процента производительности тут объективно будет.

2. Возрастающие значения в индекс ложатся гораздо быстрей, чем случайные. Возрастающие просто в конец узла B-дерева добавляются, изредка его немного перестраивая. Дерево компактное. Случайные значения летят в случайные узлы, дерево будет более "разбитое". Разница в производительности на больших объёмах вставок тоже объективно будет. Вариант 2.2 собственно только ради этого и придумывался.

3. Запросы order by id. Понятно, что обычно есть что-то вроде created_at, но всё равно удобно иметь возможность упорядочить строки в порядке создания, переиспользуя уже существующий индекс.

4. Читабельность. Ну тут целые числа однозначно приятней глазу, чем UUID.

5. Безопасность. В некоторых сценариях UUID хорош тем, что его нельзя перебирать. Обычно для таких сценариев делают отдельный столбец с UUID, если в базе id целочисленный, но иметь этот столбец уже готовым удобней. Также, когда внешние идентфикаторы всегда UUID, это в целом безопасней, т.к. про безопасность отдельных эндпоинтов не всегда люди думают, тут оно "нахаляву". Однако с вариантом 2.3 с безопасностью есть нюансы — утекает timestamp так или иначе.

6. Генерация ID на стороне клиента. Порой это бывает удобно. Я бы сказал, это вообще всегда удобно. Генерация ID на стороне сервера накладывает достаточно ощутимые ограничения на используемые протоколы и во многих случаях усложняет код.

7. Распределённая БД. К примеру у нас есть древовидная структура БД, когда в каждом филиале стоит отдельная БД (для возможности автономной работы) и данные уходят в центральную БД. В случае UUID всё выглядит гораздо проще. В случае id нужно думать чуть больше.

8. Совпадения id для разных сущностей. К примеру можно сделать join по неправильному полю. Если id не совпадают, запрос ничего не вернёт, что вероятно насторожит и не даст багу идти дальше. В противном случае может вернуться бессмысленный, но ненулевой результат.

Также есть проблема с реализацией вариантов 2.2 и 2.3, это не слишком часто используемые подходы и возможно придется использовать редкие библиотеки или писать самому. Это не великая проблема, но всё же.

Я сейчас работаю над best practices, которыми будем руководствоваться в будущем для создания сервисов. И нужно этот вопрос решить. Плюсы и минусы там и там есть. Скорей склоняюсь к UUID 2.3, т.к. считаю, что проблемы с производительностью будут малозаметны, а удобство читаемых id не перевешивает плюсов UUID. Заранее спасибо за конструктивные отзывы.
Отредактировано 22.08.2022 20:01 vsb . Предыдущая версия . Еще …
Отредактировано 19.08.2022 14:33 vsb . Предыдущая версия .
Отредактировано 19.08.2022 14:31 vsb . Предыдущая версия .
Отредактировано 19.08.2022 14:30 vsb . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.