Обновить

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

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

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

1) Доктор Иванов перевелся из клиники в Москве в клинику в Питере.
А если не перевелся, а совмещает работу, принимая в нескольких клиниках?
Такое часто бывает, когда в клинику в небольшом городе пару раз в месяц приезжают специалисты из регионального центра.
А теперь нам надо сделать выборку по всей практике этого доктора по всем клиникам.
Или сделать выборку всех врачей работающих в любой клинике сети, без дублей.

2) Терапевт Петров записался на лечение зубов к стоматологу Сидорову.
Теперь нужно ему всю информацию дублировать, потому что он теперь не доктор, а пациент. И при изменении информации (контактные данные например сменились) ее тоже менять в обеих местах.

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

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

Как я понял, надо ещё скрывать темп выдачи ид. Условно, конкурент раз в день регает новый аккаунт на сайте, чтобы темп регистраций видеть + каждый день делает запись к врачу, чтобы видеть темп записей.

Но, если есть задача это скрывать, то выглядит логичнее не тянуть это все в бд, а просто прокси реализовать, который симметричным ключем шифрует и дешифрует ид в обе стороны. Можно ещё шифротекст дополнительно шехировать и часть хеша дописать в зад, чтобы расшифровка шла в 2 этапа - сначала убедиться, что шифротекст не выдуман с головы ради перебора диапазона, потом расшифровывать. Скажем, идёт с фронта запрос "getAvailableDoctors". API вернул [1, 123, 5009]

Прокси заменяет на что-то типа ['fRvjYY4-jgfrE', 'gjktvED-326sT', 'Dxgj13-juoicx'].

Аналогично, если идёт запрос doctor/Dxgj13-juoicx, то прокси проксирует далее как doctor/5009

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

Ну и да. В случае с "тяжелыми" ид типа uuid, random(int64) etc, когда надо человекочитаемый формат где-то выдать, то можно иметь алиас под задачу. Например, номер направления, чтобы показать на рецепции, явно uuid не подойдёт. Но, и 2567322 не сильно лучше тоже. Поэтому, никто не мешает под конкретную задачу иметь отдельный короткий ид. Например, int identity в рамках пары (календарная дата, ид больницы), чтобы номер был коротким, но два пациента на одной рецепции не сколлидировади

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

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

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

Например если у вас может создаваться произвольное количество независимых серверов (а часть из них не дай бог вообще к интернету не подключены), то координировать инкремент между ними может быть космически сложнее, чем решить силами оператора конфликты из-за коллизий uuid, которые скорее всего ещё и никогда не возникнут.

мда, неронки лучшее и худшее что было рождено в этом столетии

По UID - есть целая сфера знаний в в алгоритмах которая затрагивает генерацию минимально-уникального указателя в рамках заданной системы. Если уж ушли в эту степь то почитайте что люди пишут по этой теме. В целом при правильно подобранном алгоритме генерации указателя в 16 байт хватит что бы промаркировать все что только душенька пожелает с гарантированной уникальностью асинхронно и офлайн. Обычно даже меньше берут (я про всякие сервисы генерации временных ссылок где уникальных указателей милионы и более в час)

UUID используют просто от лени и нежелании разбираться в теме. Вообще UUID это стандарт под узкую сферу вот только та самая лень разработчиков (оно же гарантировано уникальное, берем!) растянула UUID везде где только можно и нельзя, а ведь достаточно только почитать как формируется UUID что бы "отрезать лишнее" и получить ключ с нужной глубиной уникальности конкретно для вашей задачи, а не огромный и неповоротливый UUID

мда, неронки лучшее и худшее что было рождено в этом столетии

Как сказал один осведомленный человек - настойчивость, энергия, упорство, инициативность, энтузиазм — это отрицательные качества, если человек тупой. А нейросети возводят это ещё и в степень.

На всякий случай хочу напомнить, что UUID - это не строки, а обычный int128 (ака структура из пары hi/lo int64 или более строгая GUID-like из int32+int16+int16+int8[8]).

P.S. Ну и если что, то “ваш способ” называется TypeID - это префикс по типу + суффикс из UUIDv7. Можно смело гуглить по кейворду, включая бенчмарки, например: https://www.saybackend.com/blog/uuidv7-postgres-comparison/

Читаемость префиксов — DOC-1001 vs абракадабра

Префиксы - читают. То есть видят и знают.

Безопасность через префикс: даже зная ID, не знаешь префикс

Объясните мне, дураку, почему прочитанный и известный префикс - неизвестен?

UUID, Безопасность: нельзя угадать

Безопастность - это не про надежду на хаос.

Скорость: все еще строки, все еще медленно

У вас то «байты», то «строки». Вы уж решите, что конкретно.

Вопрос: Кто из них врач, кто пациент, кто запись? Правильно, никак не понять!

Сами придумали проблему, и сами ее решили. С древних времен вообще-то принято тип хранить в отдельном поле. Тем более, что, наверное, лечащим врачам нечего делать среди пациентов, нет? Ну разве что хранить их как неких пользователей системы, тогда получается, что «доктор Пилюлькин» может проходить как пациент только в виде «пациент Пилюлькин». В общем, тоже вариант, но с душком.

Таблица в БД

Вообще не понятно, зачем там readable id, если это просто склейка префикса с обычным PK. И почему «гениальная идея» озвучена вдруг после этого факта.

Безопасность через префикс

Угадать 3-х буквенный префикс (даже 4-х) куда как проще, чем тот же uint32. Безопасностью тут и не пахнет.

Для URL мы используем не просто хэш, а контекстный хэш:

А это что такое? Почему вдруг соль гарантировано убрала коллизии?

Умный ID для глобальной сети

Опять не понял - это откуда появилось и почему именно так? Использование случайного числа в рамках целого дня даже при жесткой привязке к серверу (что само по себе странно) - это даже для ненагруженных систем плохо.

Вопрос, который вы зададите: "Если это так круто, почему нет готового пакета?"

Я не считаю, что это круто. И вам советую корону снять.

«Идею» с префиксами я использовал еще лет 12 назад. А если уж так хочется подобия распределености, то вовсе не обязательно придумывать что-то свое. Вон тот же ULID - это 48 timestamp плюс случайная часть. Возьмите 64 бита. Приделайте к ним префикс, если уж так хочется. Ну или какой-нибудь FlakeId.

Идея разделить ID по ответственности (внутренний PK / публичный непредсказуемый ключ / читаемая метка) интересная. Но в реализации есть пара мест, которые именно для медсистемы неочень, и хочется на них указать, пока кто-нибудь не унёс это в прод.

1. md5 с общей солью — это обфускация, а не безопасность.

$salted = $prefix . $this->id . config('app.key');
return $prefix . substr(md5($salted), 0, 16);

Соль одна на всё приложение, prefix и id предсказуемы → секрет по сути только app.key. При его утечке (а он есть во всех воркерах и обычно в .env) предсказываются все хэши разом. Плюс усечение до 64 бит без UNIQUE на колонке hash, может привести к тому, что коллизия молча сломает роутинг и отдаст не ту запись. Для PHI это не неудобно, а инцидент. Тут нужен честный CSPRNG: bin2hex(random_bytes(16)) + уникальный индекс.

2. Диапазоны через AUTO_INCREMENT — это потолок и пересечения.

Врачи 1000–1999 это жёсткие 1000 записей при заявленных 1000+ врачей, т.е. упрёмся достаточно быстро. А автоматическое расширение

$newEnd = $prefix->range_end + ($total);

просто пишет число в конфиг, а AUTO_INCREMENT про этот range_end ничего не знает, и соседние диапазоны спокойно пересекутся. Контроль тут иллюзорный.

3. Региональный ID ловит коллизии на потоке.

// Москва: 01 + 20240320 + 1234

4 цифры random = 10k значений на регион в день, при 1M записей в год коллизии гарантированы. Это самодельный Snowflake без счётчика. Плюс конкатенация в число съест ведущий ноль, и substr($id, 0, 2) для разбора региона сломается.

По сути гибрид — это INT PK + Hashids в URL, и это давно есть в пакетах (тот же vinkla/hashids), так что тезис почему этого нет в готовых решениях немного лукавит. Сам подход рабочий, но я бы взял UUIDv7 как PK (сортируемость + без фрагментации v4), публичный слой на random_bytes, а PREFIX-id оставил чисто для отображения в админке, не для роутинга и не для безопасности. Спасибо за статью 👍

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

Публикации