Комментарии 25
Стоит добавить к сравнению CBOR (https://cbor.io/) у него свои особенности и преимущества и тоже есть широкая поддержка для разных языков, в некоторых случаях получается более компактные структуры.

В статье не упомянута поддержка стримерами в разные аналитические базы типа вертики и кликхауса. Для многих — это решающий момент в выборе кодека.
Когда-то отдали предпочтение именно Avro при записи в service>kafka>dbms, ввиду хорошей интеграции.


Для RPC безусловно protobuf снимает головную боль при добавлении полей.
Более того, если часть полей тебе не нужна, то можно исключить их из схемы в одном из сервисов, тем самым снизив количество аллокаций.


Для RPC Avro добавит ещё дополнительную точку отказа — schema registry, да, он кластеризуется, даёт удобство, но тем не менее усложняет систему.

в протобафе это значение задано жестко, в зависимости от типа (0, пустая строка, false)

Это справедливо только для proto3, proto2 вполне себе поддерживает параметр [default]

Для того, чтобы описать опциональное поле, в авро, необходимо использовать union с двумя вариантами, один из которых null, а в протобафе — oneof из одного варианта.

В protobuf поддержка опциональных полей встроена. В proto v3 все поля опциональны. Что дает поддержку версионности «из коробки» и делает ненужными схемы и в 99% случаев делает ненужным one-of.

Насколько я понял статью, речь всё-таки о том, чтобы отличать вариант когда поле не передали вообще от варианта, когда поле передали с пустым (нулевым) значением. В протобаф в этой ситуации обычно используют well-known-types, которые по сути просто message оборачивающий одно поле стандартного типа. Так что корректнее, возможно, было бы написать в статье, что в протобаф это делается либо через one-of, либо через оборачивание поля в message.

protobuf wire формат поддерживает опциональные поля и в proto v2 — это во всю использовалось. Из версии 3 — это поддержку убрали зачем-то из описания формата. Но поскольку wire формат не изменился, теоретически, можно попрежнему проверять было ли поле установленно или нет.
В функциональной парадигме option — отдельный тип (другие названия maybe, nullable). Для функциональщиков one-of очень нужен для конструирования алгебраических типов.
В целом, можно сказать, что вы будете удовлетворены размером и скоростью обоих форматов.

Вот прям так уверенно… Есть другие мнения, например:


In spite of being fast per se, Avro serialization has quickly revealed performance issues in our business scenarios. We discovered that Avro serialization/deserialization routines consume much of CPU time, resulting in heavy loads and reduced throughput.

В общем-то, у нас получилось примерно так же. Avro условно "быстра" пока нагрузка держится в пределах 1000 ops. Т.е. для API — видимо, неплохо для многих случаев. Но если надо прочитать из базы сотню тысяч записей и по ним сделать отчет — Avro станет узким местом (речь про gopkg.in/linkedin/goavro).


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

Тут результат очень зависит от того, что и как делать. Если читать несколько полей из сотни, то результат у нас такой получался:


Avro-8              98437             19311 ns/op           11257 B/op        149 allocs/op
Flat-8           60032416                19.8 ns/op             0 B/op          0 allocs/op
Json-8              15333             83824 ns/op           11073 B/op        603 allocs/op

Т.е. Avro по чтению в четыре раза быстрее JSON и в 1000 раз медленнеее, чем FlatBuffers. Но это самый выгодный для FlatBuffer вариант, в других случаях по чтению он быстрее "всего" раз в 50.

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

Именно, и на каждое поле выделяется кусок память в куче. В то время как у FlatBuffers — "zero allocation".

Avro тоже кодирует целые числа с помощью zigzag encoding делая их переменной длины. Кажется, протобуф использует тот же алгоритм

Спасибо за уточнение. Добавил это в статью, чтобы не вводить в заблуждение читателей.

Стоит упомянуть о Bond от Microsoft.
Так же нет ни слова об экосистеме, в частности о расширяемости protoc компилятора. Не очень понятно зачем хранить где-то схему в случае с protobuf

SBE не использует компрессию для чисел и int32 всегда занимает 4 байта, кроме того нет опциональных полей (кроме определенных случаев), поэтому SBE по факту всегда будет больше.
SBE — это про скорость и пропускную способность, а не про размер и удобство. SBE один из самых быстрых из существующих сериализаторов/десириализаторов с пропускной способностью близкий к пропускной способности шины памяти. Основной сценарий его использования — реалтайм котировки, а не API общего назначения.

Я бы отметил кодек Cap'n Proto от автора Protobuf. Он достаточно быстрый, без лишних копирований, с гибкой схемой версионирования.

Что вам мешает использовать int4, int8, int16? Я не разводу срач, просто интересуюсь.

Никто не мешает, если эти типы подходят. Но в целом из-за принципиального отсуствия компресии SBE размер буждет больше.
А что такое int4?

Странно что не сказанно о поодержке в AVRO логических типов (в терминологии AVRO) — UUID, Decimal etc.

Оба формата поддерживают механизмы расширения системы типов (logical types в авро и well known types в протобафе). Таким образом обе схемы дополнительно поддерживают сериализацию даты и времени (timestamp) и продолжительности времени (duration).

В отличии от авро, протобаф не поддерживает decimal и UUID.
А в итоге выбор будет происходить по следующему алгоритму:
1. Если в проекте уже есть хотя бы в одном месте протобаф или авро или что-то ещё — то оно будет использовано и в остальных местах.
2. Если ни в одном месте ещё нет ничего, то будет использовано то, с чем есть больше опыта у команды или тимлида.
3. Если ещё ничего не использовано и опыта нет ни с чем, то использовано будет что-попало и оно даже будет работать, ну потому что а почему бы и нет. Систем, где производительность упрётся в скорость бинарной сериализации — кот наплакал и вряд ли это вот прямо ваш продукт (в случае, если бинарной сериализации у у вас ещё нет и команда с ней не знакома).
Иногда проект сочетает много уровней (например в робототехнике) и есть связь с маленькими контроллерами, и с вебинтерфесом (ну и еще несколько уровней), и тогда придется, либо использовать разные сериализации, либо найти компроммисс, вот для этого нужно сравнение, чтобы хоть немного уменьшить случайность в 3 и во 2 случаях, а там, и реализации готовые нужно смотреть, и размер сериализованных данных для конкретных применений, и реакции на ошибки, и еще массу нюансов которые как раз и полезно было бы в статье увидеть.
В авро можно указать любое, допустимое значение, в протобафе это значение задано жестко, в зависимости от типа (0, пустая строка, false).

В protobuf 2 было указание дефолтного значения для необязательных полей. В реализации для C# (неофициальной, а той, что ближе к C#-style) прекрасно и в 3-й части можно указывать DefaultValue.

Мой опыт с протобуфом на net core проекте:


  1. Быстрее BinaryFormatter (незначительно, выловили только в бенчмарках)
  2. Компактнее
  3. Нет сохранения ссылочной целостности (если есть циклы — не подходит)
  4. Удобный, строго типизированный API
  5. Обратная совместимость

Если в будущем придется иметь дело с сериализацией, мой личный выбор — протобуф

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.