В среде разработчиков часто бытует мнение, что протокол сериализации protobuf и его реализация — это особая, выдающаяся технология, способная решить все реальные и потенциальные проблемы с производительность одним фактом своего применения в проекте. Возможно на такое восприятие влияет простота применения этой технологии и авторитет самой компании Google.

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

Все последующее изложение касается только реализации protobuf на платформе Java. Также в основном описана версия 2.6.1, хотя в уже выпущенной версии 3.0.0 принципиальных изменений я также не увидел.

Для замеров производительности я использовал рабоче-крестьянский метод. Применить JMH мне помешала лень, а также тот факт, что при устранении всех оптимизаций компилятора значения измерений получились бы ещё хуже, в то время как даже уже их текущая отвратительность меня вполне устраивала.

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

maven-проект с уже подключенными зависимостями для самостоятельного исследования можно взять на github.

0. Необходимость препроцессинга


Это наименьшая проблема, даже не хотел включать ее в перечень, но для полноты пусть будет упомянута. Для того чтобы получить java-код необходимо запустить компилятор protoc. Некоторая проблема есть в том, что этот компилятор представляет собой нативное приложение и на каждой из платформ исполняемый файл будет своим, поэтому обойтись простым подключением maven-плагина не получится. Как минимум нужна переменная окружения на машинах разработчиков и на CI-сервере, которая будет указывать на исполняемый файл, и после этого его уже можно запускать из maven/ant сценария.

Как вариант, можно сделать maven-pluging, который держит в ресурсах все бинарники и распаковывает из себя нужный под текущую платформу в временную папку, откуда и запускает его. Не знаю, может такой кто-то уже и сделал.

В общем, невелик грех, поэтому простим.

1. Непрактичный код


К сожалению, для платформы Java генератор protoc производит очень непрактичный код. Вместо того, чтобы сгенерировать чистенькие anemic-контейнеры и отдельно сериализаторы к ним, генератор упихивает все в один большой класс с подклассами. Генерируемые бины нельзя ни внедрить в свою иерархию, ни даже банально заимплементировать интерфейс java.util.Serializable для спихивания бинов на куда-нибудь сторону. В общем они годятся только в качестве узкоспециализированных DTO. Если вас это устраивает — то это и не проблема вовсе, только не заглядывайте внутрь.

2. Излишнее копирование — низкая производительность


Собственно вот тут у меня начались уже совершенно объективные проблемы. Генерируемый код для каждой описываемой сущности (назовем ее «Bean») создает два класса (и один интерфейс, но он не важен в данном контексте). Первый класс — это immutable Bean который представляет собой read-only слепок данных, второй класс — это mutable Bean.Builder, который уже можно править и устанавливать значения.

Зачем так сделано, осталось непонятным. Кто-то говорит, что авторы входят в секту адептов ФП; кто-то утверждает что так они пытались избавится от циклических зависимостей при сериализации (как это им помогло?); кто-то говорит, что protobuf первой версии работал только с mutable-классами, а глупые люди стреляли при этом себе в ноги.

Можно было бы сказать, что на вкус и цвет архитектуры разные, но при таком дизайне для того чтобы получить байтовое представление вам нужно создать Bean.Builder, заполнить его, затем вызвать метод build(). Для того чтобы изменить бин, нужно создать его билдер через метод toBuilder(), изменить значение и затем вызвать build().

И все ничего, только при каждом вызове build() и toBuilder() происходит копирование всех полей из экземпляра одного класса в экземпляр другого класса. Если все что вам нужно — это получить байтовый массив для сериализации или изменить пару полей, то это копирование сильно мешает. Кроме того, в этом методе похоже (я сейчас выясняю) присутствует многолетняя проблема, которая приводит к тому, что копируются даже те поля, значения которых даже не были установлены в билдере.

Вы вряд ли заметите это, если у вас мелкие бины с небольшим количеством полей. Однако мне в наследство досталась целая библиотека, количество полей в отдельных бинах которой достигало трех сотен. Вызов метода build() для такого бина занимает около 50мкс в моем случае, что позволяет обработать не более 20000 бинов в секунду.

Ирония в том, что в моем случае другие тесты показывают, что сохранение подобного бина через Jackson/JSON в два-три раза быстрее (в случае если проинициализированы не все поля и большую часть полей можно не сериализовать).

3. Потеря ссылочности


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

Другими словами если у вас есть bean1 и bean2, которые ссылаются друг на друга, то при сериализации-десериализации вы получите bean1, который ссылается на бин bean3; а также bean2, который ссылается на бин bean4.

Уверен, что в подавляющем большинстве случаев такая функциональность не нужна и даже противопоказана в простых DTO. Однако эта проблема проявляется и в более естественных случаях. Например, если вы добавите один и тот же бин в коллекцию 100 раз, он будет сохранен все 100 раз, а не одиножды. Или вы сериализуете список лотов (товаров). Каждый из лотов представляет собой мелкий бин с описанием (количество, цена, дата), а также со ссылкой на развесистое описание продукта. Если сохранять в лоб, то описание продукта будет сериализовано столько раз, сколько существует лотов, даже если все лоты указывают на один и тот же продукт. Решением этой проблемы будет отдельное сохранение продуктов в виде словаря, но это уже дополнительные действия — и при сериализации, и при десереализации.

Описанное поведение является абсолютно ожидаемым и естественным для текстовых форматов типа JSON/XML. Но вот от бинарного формата ожидаешь несколько другого, тем более, что штатная сериализация Java в этом плане работает ровно так, как и ожидается.

4. Компактность под вопросом


Бытует мнение, что protobuf является суперкомпактным форматом. На самом деле компактность сериализации обеспечивается всего несколькими факторами:

  • Реализованы и используются по-умолчанию типы var-int и var-long — как знаковые, так и для беззнаковые. Поля таких типов позволяют сэкономить место, в случае если реальные значения в этих полях невелики. Иными словами, если распределение по всему диапазону значений неравномерно и основная масса значений сконцентрирована около нуля. Например, при сохранении значения 23L оно займет всего лишь один байт вместо восьми. Но с другой стороны, если вы сохраните Long.MAX_VALUE, то такое значение займет уже все десять байт.

  • Вместо полных метаданных (имен полей) сохраняются только числовые идентификаторы полей. Собственно ради этого мы и указываем идентификаторы в proto-файлах и именно поэтому они должны быть уникальными и неизменными. Идентификаторы сохраняются в полях типа var-int, поэтому есть смысл начинать их именно с 1.

  • Не сохраняются поля, для которых не было установки значений через сеттеры. Для этого protobuf при установке значений через сеттеры также устанавливает в отдельной битовой маске соответствующий полю бит. Тут не обошлось без проблем, поскольку при установке значения 0L такой бит все равно взводится, хотя очевидно, что сохранять такое поле нет необходимости, поскольку в большинстве языков 0 — это значение по-умолчанию. Например, Jackson при сериализации, когда решает сериализовать это поле или нет, смотрит на непосредственное значение поля.

И все это замечательно, но вот только если мы посмотрим на байтовое представление DTO среднего (но за всех говорить не буду) современного сервиса, то увидим, что большую часть места будут занимать строки, а не примитивы. Это логины, имена, названия, описания, комментарии, URI ресурсов, причем часто в нескольких вариантах (разрешениях для картинок). Что делает protobuf со строками? В целом ничего особого — просто сохраняет их в поток в виде UTF-8. При этом помним, что национальные символы в UTF-8 занимают по два, а то и по три байта.

Предположим, приложение генерирует такие данные, что в процентном соотношении в байтовом представлении строки занимают 75%, а примитивы занимают 25%. В таком случае, даже если наш алгоритм оптимизации примитивов сократит необходимое для их хранения место до нуля, мы получим экономию всего в 1/4.

В некоторых случаях компактность сериализация является весьма критичной, например для мобильных приложений в условиях плохой/дорогой связи. В таких случаях без дополнительной компрессии поверх protobuf не обойтись, иначе мы будем впустую гонять избыточные данные в строках. Но тогда вдруг выясняется, что аналогичный комплект [JSON+GZIP] при сериализации дает несильно больший размер по сравнению с [PROTOBUF+ZIP]. Конечно, вариант [JSON+GZIP] будет также потреблять больше ресурсов CPU при работе, но в тоже время, он зачастую также является еще и более удобным.

protoc v3


В protobuf третьей версии появился новый режим генерации «Java Nano». Его еще нет в документации, а runtime этого режима еще в стадии alpha, но пользоваться им можно уже сейчас при помощи переключателя "--javanano_out".

В этом режиме генератор создает анемичные бины с публичными полями (без сеттеров и без геттеров) и с простыми методами сериализации. Лишнего копирования нет, поэтому проблема #2 решена. Остальные проблемы остались, более того при наличии циклических ссылок сериализатор выпадает в StackOverflowError.

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

protostuff


Альтернативная реализация протокола protobuf. В бою не испытывал, но на первый взгляд выглядит очень добротно. Не требует proto-файлов (однако умеет с ними работать, если это необходимо), поэтому решены проблемы #0, #1 и #2. Кроме этого умеет сохранять в свой собственный формат, а также в JSON, XML и YAML. Также интересной является возможность перегонять данные из одного формата в другой потоком, без необходимости полной десериализации в промежуточный бин.

К сожалению, если отдать на сериализацию обычный POJO без схемы, аннотаций и без proto-файлов (так тоже можно), protostuff будет сохранять все поля объекта подряд, в независимости от того были они проинициализированы значением или нет, а это снова сильно бьет по компактности в случае, когда заполнены не все поля. Но насколько я вижу, такое поведение при желании можно подправить, переопределив пару классов.
Поделиться с друзьями
-->

Комментарии (50)


  1. vintage
    14.09.2016 23:50
    -13

    Веб в своё время расцвёл именно благодаря текстовым протоколам. Сейчас же многие "закручивают гайки" экономя на спичках, тормозя инновации под предлогом оптимизации.


    Не хотиnе ли попробовать реализовать сериализатор в формат tree? Он с одной стороны читаем человеком, с другой — весьма гибок, а с третьей позволяет передавать бинарные данные без экранирования (только сплит по сепаратору). В сериализованном виде может получиться что-то типа такого:


    schema Bean Bean id int64
    
    schema InnerBean Bean
        value1 int64
        value2 int64
        value3 int64
        value4 int64
        value5 int64
        value6 InnerBean id
    
    schema OuterBean Bean
        descr string
        bean1 InnerBean id    
        bean2 InnerBean id    
        bean3 InnerBean id    
    
    InnerBean
        id \1
        value1 \3lmt8r
        value2 \i23dbi
        value3 \eui2f3b
        value4 \ewviubi4
        value5 \342
        value4 \2
    
    InnerBean
        id \2
        value1 \3lmt8r
        value2 \i23dbi
        value3 \eui2f3b
        value4 \ewviubi4
        value5 \7vvvvvvvvvv
        value4 \2
    
    InnerBean
        id \3
        value1 \3lmt8r
        value2 \i23dbi
        value3 \eui2f3b
        value4 \ewviubi4
        value5 \342
        value4 \1
    
    OuterBean
        id \4
        descr
            \this is multiline description
            \of bean #4
        bean1 \1
        bean2 \2
        bean2 \3

    Разбирается это тривиальным парсером. Избыточность нивелируется простейшим алгоритмом сжатия. Числа я тут записал в base32, но можно и сырыми данными.


    1. MzMz
      15.09.2016 08:14
      +2

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


    1. AndreySu
      15.09.2016 08:35

      При чем здесь Веб? Есть много других областей где применяется передача данных по сети.


      1. vintage
        15.09.2016 08:49
        -3

        При чём здесь передача данных по сети? Есть много других областей, где применяется сериализация объектов.


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


        1. AndreySu
          15.09.2016 08:53
          +1

          Часто встречаю в своей работе что не нужен гибкий протокол и ему не нужна куча различных применений и распространенности и такие протоколы используем в С++ коде. А как можно говорить о стабильности у универсального протокола типа JSON? Что даёт ему +10 к стабильности или скорости работы?


          1. vintage
            15.09.2016 09:40
            -2

            Часто встречаю в своей работе что не нужен гибкий протокол и ему не нужна куча различных применений и распространенности и такие протоколы используем в С++ коде.

            То, что гибкость в конкретный момент не востребована, не означает, что она не потребуется в будущем (пример из статьи — внезапно выясняется, что из 100 возможных полей заполнены лишь 10 и требуется "адаптивная схема") и уж тем более не означает, что нужно намеренно выбирать наиболее дубовый протокол имеющий лишь один вариант применения.


            А как можно говорить о стабильности у универсального протокола типа JSON?

            Tree — это формат представления AST. Так что он ближе к LISP и XML, чем к JSON. Впрочем, гибкость протокола не исключает использования схем. В примере выше, схема данных прилагается к самим данным.


            Что даёт ему +10 к стабильности или скорости работы?

            Предельная простота, поддержка потоковой обработки и отсутствие необходимости декодировать строки, например.


            1. AndreySu
              15.09.2016 09:46

              «дубовый протокол» — быстр, чрезвычайно прост, занимает минимум места, не принуждает копировать/декодировать строки, применяется только там где он необходим.


              1. vintage
                15.09.2016 10:45

                Было бы странно, если бы бинарный протокол не обладал этими характеристиками. Однако, вы забыли упомянуть:


                1. Требует кодогенерации.
                2. Ограниченный набор поддерживаемых языков.
                3. Ограниченная система типов.
                4. Ограниченные возможности отладки.


    1. nwalker
      15.09.2016 16:02

      И что, для него есть production-ready реализации для Java, ObjC/Swift и какого-нибудь серверного языка?
      Не говоря уже о том, что он текстовый и наверняка не имеет кодогенерации.


      1. vintage
        15.09.2016 16:25

        Пока только D и JS/TS.


  1. xhumanoid
    15.09.2016 00:05
    +6

    0. https://www.xolstice.org/protobuf-maven-plugin/ уже давно все есть

    1. начиная с protobuf v3 одной командой в протофайле option java_multiple_files = true; отключаем генерацию большого файла, получаем набор class=>file. Править полученные протофайлы вам не запрещается, при большом желании можно расширить компилятор и он будет генерить то что вам нужно и прописывать интерфейсы какие хотите

    2. как вы предлагаете изменять byte[] в котором по определенным смещениям лежат значения изменяемой длинны? (ваш же пункт 4, чтобы что-то поменять нам нужно будет хвостик сдвинуть влево-вправо и изменить размер самого массива) Если объект в системе вроде как и готов, но возможно еще будет меняться, то перекидывается ссылка на билдер, а вот если нужно отправить в wire клиенту, то тут уже и build() вызывается и получаем готовый слепок.

    3. сами же и сказали решение проблемы, dto не должно думать как упаковать граф, а потом его распаковывать, или может JSON уже научился такое делать? с учетом того что proto v3 еще ближе приблизился к json, то упаковка графа в руках того кто упаковывает, а совсем не стандартная операция.

    4. [PROTOBUF+ZIP] vs [JSON+GZIP] это спор на уровне: у нас есть SQL и у нас есть NoSQL, в одном схема прописана и если говно пришло то оно сразу отбросится, во втором мы что-то как-то запишем-прочитаем. И далеко не у всех текста гоняются, зачастую только ID нужных элементов. К тому же сами признали увеличенную нагрузку на CPU, что для мобильных приложений очень критично. Хотите еще быстрее, с меньшей нагрузкой на CPU и без схемы, то добро пожаловать в MessagePack, только потом не жалуйтесь, что клиенты прислали очередную кашу.

    В общем у вас пожелания: я хочу бинарный формат, который работает быстро, является компактным, сохраняет и проверяет схему, сохраняет ссылочность, позволяет без копирования изменять поля прямо в byte[] и т.д.

    Лично я не знаю таких форматов и вижу противоречия в требованиях: компактный vs изменения сразу упакованного массива, компактный-быстрый vs сохраняем целиком ссылки-граф.


    1. MzMz
      15.09.2016 08:03
      +1

      0. Спасибо. Но я вижу что ему надо указывать путь к protoc, то сам он его не содержит, поэтому лично мне проще protoc запустить встроенными средстваи ant/maven

      1. Да, я про версию 3 немного написал. К сожалению править сгенерированные файлы конечно нельзя — максимум положить рядом diff.patch и применять его после генерации.

      2. Нене. Проблема излишнего копирования в текущей реализации заключается в копировании данных между Bean.Builder и Bean. Такое копирование можно исключить, так как это сделал protostuff и javanano@protobuf.v3. А что еще более сильной оптимизации, там есть варианты, поскольку формат не очень сложный. Например наши инженеры написали конвертор byte[] -> byte[], который позволяет поменять список значений полей без необходимости полной десериализации.

      В целом меня все устраивает. За исключением проблемы #2, которую сейчас пофиксим пул-реквестом. Цель статьи — напомнить, что серебрянной пули не существует :)


      1. xhumanoid
        15.09.2016 08:14
        -1

        0. да, можно указать, но он по умолчанию из окружения вытягивается, а в окружение почти все знакомые ставят apt-get/yum/port install protobuf-java

        Я пока не видел людей которые считают protobuf серебрянной пулей =) чаще встречаю которые считают, что json это верх совершенства и пихают его везде, а потом ловят несогласованность форматов в рантайме. В свое время лично для меня proto решил много проблем, так как позволяет высунуть контракт протокола на сервисы и гарантировать его соблюдения.

        Со строками чаще проблемы не в том что копируются туда-сюда, а в самом кодировании-декодировании в UTF8, так как в java локальное представление Unicode, вот тут CPU и проседает частенько =(


  1. samodum
    15.09.2016 01:20
    -2

    >«В среде разработчиков часто бытует мнение, что протокол сериализации protobuf и его реализация — это особая, выдающаяся технология, способная решить все реальные и потенциальные проблемы»

    Ни один нормальный разработчик никогда полностью не доверяет какой-то технологии и всегда относится к ней с сомнением.
    Если это не так — присмотритесь к такому разработчику: не пора ли его уволить


  1. kemsky
    15.09.2016 03:05

    Формат/протокол сам по себе мало о чем говорит, все чаще решает конкретная реализация и распространенность. Все писали, что на андроиде не надо использовать встроенную джавовскую сериализацию, строго говоря не так уж и медленно она работала, однако попалась либа https://github.com/RuedigerMoeller/fast-serialization, которая для меня упростила жизнь многократно. Долгое время на флеше использовал AMF, прекрасный протокол: сжатие, упаковка, циклические ссылки, бинарный, потом на одном проекте надо было json, флешовый нативный json парсил строки мгновенно, меньше миллисекунды, надо было только потом замапить на модель, чуть мелденнее AMF, но не критично. А потом soap на мобильном устройстве и в принципе ничего страшного, немного оптимизаций :) Один раз попробовали заменить RMI отправкой данных (фактически байт массивов) руками по сокету, и никакого выигрыша не удалось достичь сходу, не все так просто. CORBA ну тот же ваш протобуф :) ничем не запомнился, кроме мучительного его изучения. Снова андроид — AIDL… А можно, между прочим, и парсеры XML повыбирать и подходы разные попробовать.


  1. kobra
    15.09.2016 08:03
    +3

    Главное преимущество protobuf что вы пишите один код для всех языков программирования. Если вам легче написать json parser на пяти языках для пяти различных бэкенд то пожалуйста. Для меня мое время важнее. Также не стоит рассматривать protobuf messages как бины — это прежде всего обертка для передаваемых данных между серверами. Ну и конечно большим плюсом protobuf — недавний релиз grpc.


    1. MzMz
      15.09.2016 08:08

      Я вот не знаю ни одного языка/платформы для которой не существовал бы готовый парсер JSON, поэтому максимум, что нужно сделать — это нарисовать мэппинг между полями бина и полями JSON, и то как правило, это требуется не всегда.

      Но идея, конечно, верная. Это не сущности, а чистые DTO, только вот иногда хочется эти данные быстро перегнать в другой формат и с текущей реализацией это требует дополнительных усилий. Ну или сразу использовать protostuff, который умеет все из коробки.


      1. kobra
        15.09.2016 10:33

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


        1. kefirfromperm
          15.09.2016 11:12

          Во-первых, класс-обертку не обязательно создавать. Маппинг в существующие классы натыкал и нормально. Можно ассоциативные массивы использовать. Во-вторых, из XML, JSON, CSV их можно нагенерировать спокойно. В-третьих, а с protobuf'ом будет не так разве?


          1. kobra
            15.09.2016 11:19

            1. Вот именно. А в случае protobuf вам не нужны существующие классы, они создаються с protobuf message. Можно и ассоциативные массивы, а можно и в строке все держать. Только для чего?

            2. Какая технология позволяет сгенерироват класс для большинства платформ, с поддержкой сериализации с XML, JSON, CSV? (кроме клонов protobuf).

            3. Смотрите п.1.


            1. kefirfromperm
              15.09.2016 12:08

              1. Если у меня есть существующие классы, зачем мне что-то ещё? Не понятно.
              2. Ну JAXB там для Java. xsd.exe для C#. На ум сразу приходят.
              3. С протобуфом всё аналогично.


              1. kobra
                15.09.2016 12:46

                1. Если вы написали классы для своих данных + написали мэппинг для XML или json то конечно вам уже ничего не нужно. Но если вы только начали писать проект, то вы описываете только как выглядят ваши данные и все! Protobuf сам позаботится о создании классов и их сериализации.

                2. Вы так и не ответили на вопрос. У вас для каждой платформы другая технология. Protobuf — это одна технология. Вы можете переслать объект с бэкенд с++ или go до Java. И все будет работать из коробки. Вам нужно только заполнит обьект значениями.

                PS У меня ощущение что вы не совсем понимаете как работает protobuf и какие задачи стояли перед его разработчиками.


                1. kefirfromperm
                  15.09.2016 13:04

                  1. Если у меня проект начинается с нуля. Я пишу класс, помечаю его аннотацией @XmlElement. И всё. Кроме того. со своими классами я могу делать все что захочу. Захотел — транзитивные поля добавил. Захотел — методы написал. С классами протобуфа, я так понимаю, ничего подобного не выйдет.

                  2. Ну если вы хотите жести жестокой, то есть такая технология WSDL. Так вот по этой самой WSDL вы можете на любой практически платформе (думаю что на гораздо большем количеств платформ, чем протобуф умеет) нагенерировать классов и интерфейсов. Вообще слабо понимаю зачем, начиная проект с нуля, начинать его на нескольких платформах генерировать классы для разных платформ. модель данных поменяли — перегенерируем классы для всех модулей на разных платформах, разрабатываемые разными командами? Единственный сценарий — это к существующему сервису цепляться. Тут WSDL в плане генерации фору даст всем. Так что совсем не аргумент.


                  1. kobra
                    15.09.2016 14:19

                    Не могу спорить о WSDL поскольку никогда с ним не работал, но насколько успел понять это только описание сервиса (без генерации кода). На мой взгляд gRPC помощнее будет. Protobuf генерирует только классы для передачи между сервисами. Они и не должны иметь какой-то внутренние методы.

                    И вы в своем разговоре привязаны к xml, но это не панацея. xml данные слишком велики по сравнению с protobuf. И конечно парсинг xml тоже в несколько раз медленнее. Для компаний типа Facebook, Microsoft, Google эти оптимизации помогают сэкономить миллионы.

                    Каждой технологии своё применение. Не существует идеального решения. И если вам удобнее передавать xml между сервисами то передавайте. Предлагаю закрыть это обсуждение.


            1. kefirfromperm
              15.09.2016 12:10

              Нагенерит вам протобуф классов, а вы потом ещё маппинг этих классов на свои делаете?


  1. kefirfromperm
    15.09.2016 08:58
    -6

    Спасибо. Теперь знаю что не стоит тратить на него время.


    1. MzMz
      15.09.2016 09:13
      +1

      Я такого не говорил :) Вообще это очень неплохая технология. Просто есть отдельные проблемы, плюс не надо ее сильно переоценивать.


      1. kefirfromperm
        15.09.2016 09:35
        -1

        Ну проблема с циклическими ссылками и, насколько я понял, дополнительная сложность в разработке (т.е. нельзя просто настаивть аннотации и сериализовать бин) нивелируют для меня все возможные преимущества.


        1. MzMz
          15.09.2016 10:31

          Аннотированные POJO в формат protobuf умеет сериализовать библиотека protostuff. Со ссылочностью в protobuf, да, все непросто — как минимум придется все запихивать в отдельные словари, а ссылочность обеспечивать через идентификаторы. Или городить даже что-то более серьезное.


          1. kefirfromperm
            15.09.2016 11:07

            Должна быть очень веская причина, чтобы все это городить.


  1. Rathil
    15.09.2016 10:20
    +2

    Как правильно заметил сам автор, большая часть «описанных» проблем — это проблемы именно реализации под JAVA. Конечно, в других реализациях есть свои подводные камни.
    Что касается ссылок друг на друга — весьма легко обходится простым изменением структуры хранения в протобафе, вводом дополнительного контейнера, где идут объекты ссылающиеся друг на друга, ну или вводом ID на ссылающийся объект.

    Нет идеальных вещей, нужно уметь вещи использовать и адаптировать под себя.


    1. kefirfromperm
      15.09.2016 11:09

      Идеальных вещей не бывает, потому что всё есть результат компромиссов. Но.

      Вот все же знают что делать с циклическими ссылками. Почему такое решение не применено в самом протоколе? Чем оно его так усложнит? На какие компромиссы придется пойти?


  1. Elsedar
    15.09.2016 12:52
    -1

    Извините, но, мне кажется, что ваши заявления слишком преувеличены, впрочем вы и сами это подтверждаете. И почему вы пишете бин, вместо привычного сообщение?

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

    4. Сами критикуете и сами же оправдываете. Все так, но здесь и нет никаких недостатков, одни очевидные плюсы. Нужно сжатие — используйте дополнительно сжатие.

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


    1. MzMz
      15.09.2016 13:06

      3. На мой взгляд так не кажется. Я сериализую объектную модель в бинарный формат. При десериализации я ожидаю получить аналогичную объектную модель.

      Извините, но мне кажется, что вы преувеличиваете мои преувеличения. Я хотел обозначить важные и не всегда очевидные моменты, которые могут повлиять на принятие решения. Я не выношу вердиктов или оценок, просто стараюсь приводить факты.


  1. Bozaro
    15.09.2016 13:05

    Я не понял претензию к строкам.
    Предложение хранить строки в национальных кодировках а-ля CP-1251 порождает гораздо больше проблем, чем решает.


    1. MzMz
      15.09.2016 13:08

      Претензий к строкам нет. Строки — это прекрасно. Прекраснее строк могут быть только Unicode-строки.

      Суть изложенного в том, что при наличии в DTO большого количества строк все оптимизации примитивов, реализованные в protobuf на самом деле погоды не делают.


      1. solver
        15.09.2016 14:07
        -1

        Вообще очень странные утверждения.
        Аля: «При наличии колес у машины, присутствие магнитолы погоды не делает.»

        Как вы вообще логически увязываете всю ту кашу которую описали?
        Т.е. не надо оптимизаций никаких делать если в протоколе есть строки?
        А т.к. в любом протоколе строки есть, то забейте на любые оптимизации?
        Вы людям голову морочите фигней какой-то банальной…


        1. MzMz
          15.09.2016 14:32

          Передайте своей Але, что, если она любит машины, то формулировка будет звучать так: «При наличии полного багажника бетонных плит, присутствие брызговиков Sparco на общие ТТХ машины не повлияет».


          1. solver
            15.09.2016 17:12

            Передал, она сказала, что это все хорошо ровно в одном случае. Если мы говорим о грузовике для перевозки плит.
            Но простобаф это библиотека общего назначения. У меня вот в нем 90% сообщений это идетнификаторы (Long). String летают редко.
            И подозреваю, что я не один такой.
            А вот вы далеко идущие выводы делаете только по одному юзкейсу. Непрофессионально это.


  1. Bozaro
    15.09.2016 13:47
    +1

    Проблему постоянного копирования данных решали в рамках реализации Cap’n Proto (https://capnproto.org).


    1. babylon
      15.09.2016 14:23

      Bozaro, один Ваш комментарий стоит всего топика.


    1. MzMz
      15.09.2016 14:48

      Cпасибо, отличная ссылка. Но это не совсем protobuf. Они реализуют байтовое представление с фиксированной шириной полей, в результате чего для изменения данных нужно знать только смещение. Данные с изменяемой длиной хранятся ссылками. Незаполненное место ликвидируется при пересылке при помощи своего легкого алгоритма компрессии. То есть можно придумать много отличных форматов, но статья про protobuf.

      Ну и плюс они подтверждают:

      When bandwidth really matters, you should apply general-purpose compression, like zlib or LZ4, regardless of your encoding format.


    1. xhumanoid
      15.09.2016 15:57

      смотрели в свое время, но если брать список по топику:

      те же c++, те же зависимости бинарного кодогенератора по схеме от os где запускаешь

      честное признание на их же сайте: Currently it only beats Protobufs in realistic-ish end-to-end benchmarks by around 2x-5x. We can do better. То есть ни о какой разнице на порядки разговор не идет. К тому же учитывая следующий абзац сравнение было на c++ версии, что происходит в java неизвестно

      для java нету оффициального сериализатора, а сторонний заброшен с февраля (Cap’n Proto’s reference implementation is in C++. Implementations in other languages are maintained by respective authors and have not been reviewed by me)

      как результат: взять такой продукт в продакшен вместо протестированного protobuf лично я не решусь никогда


      1. Avitella
        15.09.2016 20:05

        Потому что Cap'n Proto's — это формат хранения данных, а не передачи.

        P.S. Лучше брать flatbuffers для этой цели.


        1. xhumanoid
          15.09.2016 20:49
          -1

          Cap’n Proto is an insanely fast data interchange format and capability-based RPC system. Think JSON, except binary. Or think Protocol Buffers, except faster.

          так что разработчики не согласны с вами, что это «формат хранения данных, а не передачи».

          p.s. лучше брать то, что решает конкретную задачу


  1. remal
    15.09.2016 16:53

    Решение для кроссплатформенной сборки можно подсмотреть тут: https://github.com/grpc/grpc-java (после строки «For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin»)

    compile у protobuf-maven-plugin'а запускает protocArtifact, а compile-custom — pluginArtifact

    ЗЫ: У меня os-maven-plugin работал только на maven 3.3 и выше.


  1. alexhott
    15.09.2016 20:14

    Столкнулся с протобуфером пытаясь из PHP сним поработать, но нормального плагина не нашел и затею похоронил.
    Вобще идея мне понравилась и если не пытаться приспособить протобуфер под свой проект, а построить проект под протобуфер то можно реально сэкноноить на хранении и ускорить обмен данными


  1. billyevans
    15.09.2016 21:16

    Есть довольно интересные еще варианты flatbuffers и avro. Для каких то сценариев использования они могут подходить лучше.


    1. igor_suhorukov
      16.09.2016 00:42

      Есть еще Thrift и отличная библиотека swift для него, которая генерирует IDL на основе объектной модели.


  1. babylon
    16.09.2016 04:23

    На PHP я использую https://github.com/rybakit/msgpack.php