Pull to refresh

Comments 17

Спасибо за вопрос!

Protobuf - это действительно отличный инструмент, но его основной сценарий использования - это обмен сообщениями, а не их хранение. Protobuf не предусматривает стандартного механизма для идентификации и распознавания сообщений в произвольном потоке данных, и предполагает, что канал связи будет заполнен исключительно protobuf-сообщениями, соответствующими единой заранее определённой схеме. Более того, protobuf не задаёт стандартного формата заголовка сообщения, поэтому эту задачу разработчики вынуждены решать самостоятельно.
Ещё один момент, на который стоит обратить внимание, — это кросс-языковая поддержка. Да, она является сильной стороной protobuf, но и здесь есть свои подводные камни. Например, документация прямо указывает, что имплементация сериализации и десериализации может немного отличаться в зависимости от платформы, особенно в отношении значений по умолчанию. Конкретный пример: числовые типы в Rust имеют чётко определённые значения по умолчанию (например,0), тогда как в JavaScript такой концепции просто нет. В результате поле вида field: 0, сериализованное на Rust, и то же самое поле, сериализованное на JavaScript, могут дать совершенно разные наборы байт. Причина в том, что Rust по умолчанию не передаёт значения, совпадающие со значениями по умолчанию, считая это излишним, а JavaScript будет передавать их явно. Такой, казалось бы, незначительный нюанс может стать источником серьёзных проблем при интеграции.

А мне как раз хотелось иметь что-то под рукой, что позволит не только эффективно обмениваться сообщениями, но и надёжно хранить их и без проблем распознавать в смешанных («замусоренных») потоках. И да, "префильтрация", когда я могу "заглянуть" в часть сообщения и понять - нужно ли мне его продолжать парсить.

https://protobuf.dev/programming-guides/techniques Self-Describing messages. Тут же в оф доках на протобаф вы можете встретить даже рекомендации с каким расширением хранить прото файлы - протобаф еще как используется для той же самой задачи что вы тут и описали.

If you want to write multiple messages to a single file or stream, it is up to you to keep track of where one message ends and the next begins. The Protocol Buffer wire format is not self-delimiting, so protocol buffer parsers cannot determine where a message ends on their own. 

All that said, the reason that this functionality is not included in the Protocol Buffer library is because we have never had a use for it inside Google.

This technique requires support for dynamic messages using descriptors. Check that your platforms support this feature before using self-describing messages.

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

А ведь очень точно подмечено! Но всё же brec не вносит новый стандарт — его назначение скорее для использования в закрытой системе, где данные не выходят за пределы этой системы, а сам brec - в первую очередь инструмент. То есть я изначально не ставил перед собой такую цель ))

выглядит как сводный брат какого-нибудь asn ber

Кстати да. Не знаком со спецификой Rust и интересно было бы сравнить, только с jsonb

Спасибо за ваши вопросы. Я отвечу сразу по JSONL и JSONB.

JSONL - это классическое текстовое представление, которое не позволяет эффективно фильтровать данные по критериям без полного парсинга, от чего будет страдать производительность поиска/фильтрации. Даже если библиотека включает "читалку", механизм предфильтрации редко доступен из коробки. Большинство известных мне крейтов поддерживают только чтение и запись, а более сложные операции остаются на откуп разработчику. Brec же предоставляет такие инструменты изначально. Мне кажется, что протокол без готовых инструментов недостаточен для полноценной работы.

JSONB действительно имеет некоторое сходство с brec, но если рассматривать его отдельно от PostgreSQL, остаются вопросы поиска и фильтрации. Обычно библиотеки, крейты jsonb предоставляют лишь бинарное представление JSON без развитых инструментов.

В целом, вопрос что лучше, что эффективнее - порочен по своей природе :) JSONL, JSONB и brec решают разные задачи. Если нужен простой обмен сообщениями, я бы выбрал JSONB или даже bincode (мне он видится более лёгким). Но когда речь идёт о хранении и быстром доступе к структурированным данным, приходится думать не только о протоколе, но и о инстументах для него. Собственно brec и пытается решить эту задачу, давая не только сам по себе протокол (то есть возможность его определить), но и инструменты к нему.

Но в конечном итоге, выбор между этими (или любыми другими) форматами зависит от конкретной задачи и требований к данным.

Спасибо за развернутый ответ 👍

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

Смотрите, дело в том, что даже если JSONL позволяет построчно обрабатывать данные, внутри каждой строки у вас всё равно находится целый объект. Например, если у вас есть объект { a: number; b: string; c: Something[] }, вы не можете "заглянуть" в поле a, не пропарсив весь объект целиком. То есть вы не узнаете значение поля a, пока не получите полностью готовый объект с помощью парсинга — только тогда можно сделать проверку obj.a = ?.

Теперь представьте ситуацию: у вас 1 млн таких объектов, а вам нужны только те, у которых a == 42. Среди всего массива данных таких объектов — всего 100 (то есть 100 на 1 000 000). Вам придётся пропарсить целиком все 1 000 000 записей, чтобы найти эти 100.

При частичном парсинге (что в том числе предлагает brec, а также ряд других решений) можно заранее на уровне протокола предусмотреть такой "ключ" и поместить его в самое начало сообщения. В таком случае парсер сможет сразу проверить a == 42 и полностью пропустить парсинг остальных полей, если они не удовлетворяют условию.

Разница наглядно показана в тестах brec. Я как раз сравнивал с JSON, который построчно (как и JSONL) читается из файла. Условие теста было — принимать только те записи, которые удовлетворяют условию. В итоге только за счёт того, что brec пропускал ненужные операции парсинга, он проверил ~1 Гб данных за ~300 мс, в то время как у JSON на это ушло ~600 мс. Это не потому что JSON медленный, а потому что он вынужден парсить запись целиком.

Таким образом мы вновь возвращаемся к целеполаганию. Если вы просто обмениваетесь сообщениями и вам не надо ни хранить их, ни "пропускать" (фильтровать), то самый простой JSON вполне достаточен (пока не столкнётесь например с невалидными строками, но это другая история ;))

На счёт скорости вы совершенно правы. В обычном режиме (то есть без фильтрации) brec уступает в скорости JSON. Но на то есть объективные причины :)

Brec добавляет заголовки, чтобы распознать сообщение "в мусоре", и включает CRC, чтобы гарантировать, что данные не были повреждены или искажены. Эти проверки естественно увеличивают накладные расходы, поэтому скорость падает. Но даже при такой нагрузке показатели остаются вполне конкурентными:

Сплошное чтение (читаем каждый пакет):

  • brec, использование хранилища: 908 МБ, 1 000 000 записей — 987 мс

  • brec, чтение пакетов как потока: 831 МБ, 1 000 000 записей — 764 мс

  • JSON: 919 МБ, 1 000 000 записей — 597 мс

Чтение с фильтрацией (ищем пакеты по критерию):

  • brec, использование хранилища: 908 МБ, 140 000 записей — 612 мс

  • brec, чтение пакетов как потока: 831 МБ, 140 000 записей — 340 мс

  • JSON: 919 МБ, 140 000 записей — 608 мс

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

Разница не большая на самом деле. В пропускную способность дисков не уперлись случайно? Включите буферное чтение на 1мб плюс подмонтируйте диск из оперативки.

Даже если "упёрся", не вижу в этом особой проблемы. Чтение так или иначе предполагает работу с диском, а не с оперативной памятью. Стремиться к "теоретически чистым" метрикам производительности... ну не знаю, мне кажется это немного избыточно. Тогда мы дойдём до обсуждения: давайте проверим на DDR5, потом на DDR4? Ну вы поняли :)

Меня больше интересует практическая сторона - как работает в реальном сценарии. Хотя вы навели меня на интересную мысль: тесты я действительно делал на SSD (и даже не обратил на это внимание), а не на HDD. Возможно, стоит провести аналогичный тест на HDD и посмотреть разницу. Спасибо за идею!

Sign up to leave a comment.

Articles