Обновить
16
0

Пользователь

Отправить сообщение

Остается ощущение чрезмерно избыточного количества разных типов идентификаторов в сообщении (id, message_id и log_id). Я совершенно уверен, что можно было бы обойтись только message_id (UUIDv7), а процедура разрешения конфликтов при этом была бы вообще не нужна.

Я хочу напомнить, что таймстемп в UUIDv7 - это по сути Всемирное координированное время (UTC), не зависящее от часового пояса, но только отсчитываемое не от 0 года, а от 1970 года в миллисекундах. В каких бы точках планеты не находились участники переписки, у них на часах устройства одно и то же время UTC. Поэтому не нужна никакая синхронизация часов участников - сейчас все компьютеры и телефоны синхронизированы с UTC достаточно точно для мессенджера. Максимальная погрешность в 100 миллисекунд воспринимается человеком как мгновение.

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

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

А почему не используются UUIDv7 "для упорядочивания сообщений в пользовательском интерфейсе"? Они же последовательные, если генерятся одним процессом! Для Rust рекомендую Uuid::now_v7(). См. также статью разработчика этого метода.

А вот log_id - это явно лишний идентификатор.

Подкину для обсуждения свеженький (но уже 12.7k stars) формат TOON (https://github.com/toon-format/toon), созданный для экономной передачи в LLM огромного количества данных, вплоть до содержимого целой базы данных. Это сильно модифицированный гибрид YAML и CSV. Сам по себе он мне понравился для моих целей (а это вовсе не общение с LLM, а передача данных в 6NF). Он практически идеален: почти нет визуального мусора, многократно меньше строк, чем в JSON. Одним щелчком вставляется в Excel в правильные столбцы (я в качестве разделителей выбрал табы). Но спецификация огромная и при этом отрывочная и неудобная. Пока не сконвертировал вручную JSON в TOON (см. мой пример), на что ушло много времни, ничего не было понятно. Поэтому есть сомнения, что все конверторы будут работать одинаково, хотя их уже много понаделали. Конверсия из JSON в TOON, к сожалению, односторонняя, если я правильно понял. Возможно, что это поправят. Три часа назад вышла версия 1.0.0.
Статьи о нем:
Is JSON Dead? Meet TOON – The Token-Efficient Alternative for LLM Prompts
JSON vs TOON – Token-efficient data format for LLMs

Да, всё верно. Но идее монорельса с индивидуальными вагонетками уже много десятилетий, а есть всего три робких коммерческих реализации - в лондонском аэропорту Хитроу, в ОАЭ и в Южной Корее. И это не сетевой транспорт, а просто замена маршрутки. Дорого, громоздко, медленно, с низкой провозной способностью, на старых технологиях. В техническом плане проекты были довольно скучные, чтобы с меньшими трудностями получить государственные субсидии. Хорошая идея не выстрелила. Причины провала социальные. Идея слишком глобальна и ресурсоемка, чтобы кто-то рискнул вложить достаточно капиталов в ее масштабную реализацию. А MVP монорельса, транспортного средства и многоуровневой системы управления в гараже не создашь. Обычно в начале развития какой-то технологии возникает много конкурирующих проектов, и происходит отсев нежизнеспособных реализаций. Но монорельс легко не поменяешь на другой.

Придется признать, что к полномасштабной реализации этой идеи придется двигаться через эволюцию автомобильного транспорта: электрические роботакси, изолированные выделенные полосы (и эстакады) для роботакси без пересечений в одном уровне, где роботакси не будут скованы ПДД и действиями водителей и пешеходов, координация движения на выделенных полосах (подключенный транспорт). При этом проблема преодоления "последней мили" до дома по дорогам общего пользования тихой скоростью уже в основном решена - не придется топать пешком от станции монорельса. Более подробно можно прочитать здесь: https://habr.com/en/articles/871324/

Выделение полос для роботакси - не быстрый и проблемный процесс. Пока об этом вообще мало кто задумывается, а кто-то и возражает: выделенные полосы отнимут у автомобилистов, мы будем стоять в пробке, а роботакси будут проноситься мимо за ограждением со скоростью больше 200 км/ч сплошным потоком. Обидно же.

Генерация UUIDv7 в БД - это возможность избавиться от автоинкремента со всеми его недостатками (см. аж 7 недостатков в тексте этой новости). UUIDv7 - это просто находка для хранилищ данных (уменьшается хаос, легче проходит адаптация к изменениям, предотвращаются ошибки, интеграция систем стоновится "бесшовной" и т.д.) и для баз данных временных рядов.

Кроме того, генерация UUIDv7 на стороне базы данных нужна в для обеспечения строгой монотонности идентификаторов. "Эта монотонность в течение миллисекунды нужна для поиска причин ошибок, пагинации по ключу (keyset pagination), поиска в логах, использования в БД временных рядов и т.п."

Авторы и контрибьюторы стандарта RFC 9562, котрый ввел UUIDv7, в первую очередь думали о генерации на стороне базы данных, и лишь попутно - о распределенных системах. К сожалению, в СМИ и на форумах много мифов о UUIDv7, в частности, что UUIDv7 это в основном про генерацию на клиентах. Вот статья (на английском), которая развеивает самые популярные мифы.

Спасибо за уточнение!

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

Незначительные нарушения монотонности не влияют на производительность БД.

The same example in JSON format (JSON-6NF) and JSON Schema

6NF JSON.json

JSON Schema.json

It's better to replace type with element_type to avoid conflict with JSON Schema.

The same example in JSON format (JSON-6NF):

[
  {
    "type": "ENTITY",
    "table": "bank",
    "entity_id": "01K3Y0690AJCRFEJ2J49X6ZECY"
  },
  {
    "type": "REFERENCE",
    "table": "country_code",
    "reference_id": "01K3Y07Z94DGJWVMB0JG4YSDBV",
    "value": "US"
  },
  {
    "type": "ATTRIBUTE_OF",
    "entity_name": "bank",
    "entity_id": "01K3Y0690AJCRFEJ2J49X6ZECY",
    "table": "bank_name",
    "value": "Bank Alpha",
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z"
  },
  {
    "type": "ATTRIBUTE_REF_OF",
    "entity_name": "bank",
    "entity_id": "01K3Y0690AJCRFEJ2J49X6ZECY",
    "table": "country_code",
    "reference_id": "01K3Y07Z94DGJWVMB0JG4YSDBV",
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z"
  },
  {
    "type": "STRUCT_OF",
    "entity_name": "bank",
    "entity_id": "01K3Y0690AJCRFEJ2J49X6ZECY",
    "table": "bank_address",
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z",
    "columns": {
      "country_code": "01K3Y07Z94DGJWVMB0JG4YSDBV",
      "street": "123 Main St",
      "city": "New York",
      "zip": "10001"
    }
  },
  {
    "type": "ENTITY",
    "table": "account",
    "entity_id": "01K3Y0G45CP4GMGE94BYQ09DFM"
  },
  {
    "type": "ATTRIBUTE_OF",
    "entity_name": "account",
    "entity_id": "01K3Y0G45CP4GMGE94BYQ09DFM",
    "table": "account_balance",
    "value": 100000.5,
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z"
  },
  {
    "type": "ATTRIBUTE_OF",
    "entity_name": "account",
    "entity_id": "01K3Y0G45CP4GMGE94BYQ09DFM",
    "table": "account_expiration",
    "value": "2025-12-31T23:59:59Z",
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z"
  },
  {
    "type": "RELATIONSHIP",
    "table": "bank_x_account",
    "relationship_id": "01K3Y0NR1Q3KTA9A6J9KYPK6YB",
    "valid_from": "2023-01-01T00:00:00Z",
    "recorded_at": "2023-01-01T12:00:00Z",
    "columns": {
      "bank": "01K3Y0690AJCRFEJ2J49X6ZECY",
      "account": "01K3Y0G45CP4GMGE94BYQ09DFM"
    }
  }
]

Бэкенд - это процесс.

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

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

Субмиллисекундный сегмент в UUIDv7, сгенерированном на клиенте, не имеет смысла. Время попадания идентификаторов с клиента на сервер больше миллисекунды. Для одного клиента это может быть миллисекунда, для другого две миллисекунды и т.п. Поэтому UUIDv7 с разных клиентов при интенсивной генерации будут приходить на сервер не в том порядке, в каком они были сгенерированы. Субмиллисекундная часть имеет смысл в случае генерации на одном сервере - разными микросервисами (как в СУБД, так и в серверных языках программирования).

Стандартом RFC 9562 определено другое назначение поля variant. Кроме того, стандарт вообще запрещает парсинг UUID без крайней необходимости, в том числе извлечение таймстемпа. И для этого есть очень веские причины, указанные в стандарте. Я бы вообще запретил использовать небезопасную функцию uuid_extract_timestamp ( uuid ) в промышленном программном коде. Для аналитики, поиска багов и т.п. - другое дело.

Поправка: RFC 9562 разрешает парсинг UUIDv7 при крайней необходимости.

Этого не сделали исходя из принципа единственной ответственности. UUIDv7 минималистичен. Только ключ, и ничего более.

Есть и более практические соображения. Могут быть две таблицы, например, условно копии в разных схемах, в которых одни и те же UUIDv7 будут первичным ключом. Как понять, к какой из двух таблиц относится "подвисший в воздухе id"?

Тем не менее, есть два способа решения проблемы "подвисшего в воздухе id". Первый - отдельная таблица соответствия UUIDv7 имеющимся таблицам.

Второй способ - использование длинного идентификатора (например 160 бит), в старших разрядах которого будет UUIDv7, а в младших - ключ на таблицу метаданных (имя таблицы "подвисшего в воздухе id" и др.) и опционально контрольная сумма. Длинные идентификаторы, содержащие UUID, прямо предусмотрены стандартом RFC 9562. Но во втором способе придется хранить такие идентификаторы как строки, а не как бинарный тип UUID, что замедлит работу БД. К сожалению, в PostgreSQL нет типа данных "длинный UUID", а далекие от системного анализа разработчики считают, что и 128 бит - слишком много.

12-битная субмиллисекундная часть, конечно же, доступна пользователю, как и любой другой сегмент UUIDv7. Она обеспечивает точность приблизительно 250 наносекунд. Но если UUIDv7 не был сгенерирован в PostgreSQL, а пришел с клиента, то вместо субмиллисекундной части будет случайное значение. Поэтому функция uuid_extract_timestamp ( uuid ) работает только с первыми 48 битами таймстемпа, которые дают точность 1 миллисекунда (а не микросекунда, как Вы написали). Если нужно извлечь таймстемп с точностью 250 наносекунд, то придется делать собственную функцию. При этом не получится воспользоваться стандартным типом timestamptz, поскольку его точность всего 1 микросекунда.

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

И да, на одном бэкенде уникальность гарантирована.

Так было в UUIDv1 (см. сегмент node). Однако, если это требует централизованной координации, то это неудобно и не всегда возможно. Если же используется MAC-адрес, то это нарушает конфиденциальность. И, вдобавок, были реальные случаи совпадения MAC-адресов.

1
23 ...

Информация

В рейтинге
5 802-й
Зарегистрирован
Активность

Специализация

Системный аналитик
Ведущий
Английский язык
SQL
Базы данных
REST
XML
DWH
ETL
Greenplum
Microsoft Access
Python