2016-11-09 2 views
0

Инфраструктура и преамбулаPlayFramework: низкая производительность десериализации JSON

У меня есть PlayFramework (2.3.8) приложение, размещенного на экземпляре AWS EC2. У меня есть массив сложных объектов, который должен быть возвращен как строка JSON через веб-API. Мне нужна глубокая копия массива, при этом все дочерние объекты будут полностью загружены до самого последнего слоя. Массив имеет размер 30-100 записей, каждая запись имеет около 1-10 записей, каждая запись которых имеет до 100 свойств, в конце нет BLOB или аналогичных элементов, все это сводится к строкам/удвоениям/Ints/Bools. Я не уверен, насколько важна точная структура данных, пожалуйста, дайте мне знать, если вам нужна дополнительная информация. В результате размер файла .json составляет около 1 МБ.

Производительность десериализации этого массива ужасна, для ~ 1 МБ на моей локальной машине требуется 3-5 минут; на EC2 это занимает около 20-30 секунд.

Исходная задача: низкая производительность при использовании play.libs JSon

Мой массив объектов загружается и сохраняется как JsonNode. Это JsonNode затем направляются к ObjectMapper, который, наконец, пишет он набран аккуратно:

List<myObject> myObjects = myObjectService.getInstance().getAllObjects(); // simplified example 

JsonNode myJsonNode = Json.toJson(myObjects); // this line of code takes a huge amount of time! 

ObjectMapper om = new ObjectMapper(); 
return om.writerWithDefaultPrettyPrinter().writeValueAsString(myJsonNode); // this runs in <10 ms 

Так что я прибит виновник чтобы быть Json.toJson десериализации. Насколько я мог узнать, это разновидная библиотека Джексона, которая используется в PlayFramework.

Хотя я читал о некоторых проблемах производительности десериализации JSON, я не уверен, что мы должны говорить о нескольких сотнях секунд до нескольких секунд, а не о минутах. Во всяком случае, я попытался внедрить некоторые другие JSON-библиотеки (GSON, argonaut, flexjson), которые на самом деле не прошли гладко.

GSON

Я «просто» попытался заменить библиотеку плей-JSon с библиотекой GSON, как я сделал на другой небольшой части проекта. Он отлично работал там, но хотя у меня нет круговых ссылок, он бросает StackOverflowErrors на мое лицо, даже если я пытаюсь десериализовать крошечный созданный вручную объект. Как на моей машине dev, так и на экземпляре EC2.

FlexJson

List<myObject> myObjects = myObjectService.getInstance().getAllObjects(); // simplified example 

JSONSerializer serializer = new JSONSerializer().prettyPrint(true); 

return serializer.deepSerialize(myObjects); // returns a prettyPrinted String 

работал довольно хорошо до сих пор, она занимает лишь около 20% времени по сравнению с методом Json.toJson выше. Это может быть, однако, потому, что оно не ДЕЙСТВИТЕЛЬНО глубоко копирует объекты. Он глубоко копирует его на первом уровне, однако, поскольку моя модель обладает более сложными свойствами (с дочерьми и внуками и grandgrandchilds ...), и их довольно много, я не уверен, как здесь разбираться.

Вот пример вывода одного из моих вложенных объектов (это одна из свойств «верхнего» объекта):

"class": "com.avaje.ebean.common.BeanList", 
       "empty": false, 
       "filterMany": null, 
       "finishedFetch": true, 
       "loaderIndex": 0, 
       "modifyAdditions": null, 
       "modifyListenMode": "NONE", 
       "modifyRemovals": null, 
       "populated": true, 
       "propertyName": "elements", 
       "readOnly": false, 
       "reference": false 

Есть ли у вас какие-либо другие предложения, решения, или намекает, что может быть сломана? Я тоже думал о том, что, возможно, объекты только ПОЛНОСТЬЮ загружаются после вызова .toJson()? Тем не менее это не должно занимать такое количество времени.

Заранее благодарен!

+1

Возможно, что «объекты загружаются только ПОЛНОСТЬЮ, когда я вызываю' .toJson() '". Как я вижу, вы используете Ebean. Попробуйте [активировать ведение журнала для операторов sql] (http://stackoverflow.com/questions/9371907/where-to-see-the-logged-sql-statements-in-play2), чтобы увидеть, загружен ли граф объекта до или во время вызова 'toJson'. – marcospereira

+0

Также вы можете попробовать использовать что-то вроде VisualVM или YourKit, чтобы узнать, что замедляет работу, или сохранить сгенерированный файл json. загрузите его в память (разберешь его), а затем отрисуйте его с помощью 'toJson' (если этот процесс быстрее предыдущего, то, очевидно, что-то другое, чем json parsing/generation делает его медленнее) – Salem

+0

Спасибо за подсказку с регистрацией sql, good идея. Оказывается, что сущности и их дочерние сущности загружаются только во время Json.toJson(). Я попытался изменить fetch = FetchType.Eager для этих дочерних объектов, но это не имело никакого значения.И в основном я не вижу, какая разница, в какой момент загружаются объекты, так почему бы не во время вывода для .toJson(). Но похоже, что узкое место связано с сущностями/БД, а не с сериализацией JSON ..? –

ответ

0

TLDR: Этот вопрос не имеет ничего общего с исполнением дескрипции JSON в PlayFrameworks, а не с некоторыми проблемами eBean/database. Включение SQL-регистрации в application.conf указывало на это.


Дальнейшие замечания и мысли: Благодаря намеком marcospereira в комментариях, я прибил проблему вниз, чтобы быть выборки проблемой в игры/ebeans, а не проблема производительности JSON.

Очевидно, что мои сущности сначала загружаются ленивыми (/ flat), включив ведение журнала SQL. Я видел, что правильные подготовленные SELECT только запускаются, когда мой код попадает в .toJson(). Таким образом, многие дочерние объекты извлекаются из базы данных только при вызове .toJson(), что приводит к нескольким сотням SELECT и, следовательно, достаточно времени для завершения.

Немного с весом экземпляров RDS я нашел некоторые очень странные результаты. Это НЕ ДЕЙСТВИТЕЛЬНО относится к первому заданию, но я хочу поделиться своими выводами, возможно, это может помочь кому-то. Читайте об этом в разделе ниже.

RDS масштабирования экспериментируют ...

В моей Dev среде (t1.micro) Я подключил скопированный экземпляр моей прод DB на небольшой RDS, например (db.t2.micro), чтобы увидеть если что-то изменится.

Моя среда prod (t2.large) + prod RDS (db.t2.large) заняла около 19,5 секунд, чтобы завершить вызов API. Новая среда Dev (t1.micro + db.t2.micro), которая слабее как для вычислений, так и для db, занимала всего около 10,5 с, что весьма неубедительно, так как в основном оба экземпляра управляли одним и тем же кодом, только указав на другой сервер БД (с идентичным содержимым db). Я переключил Dev DB на db.m4.large, чтобы убедиться, что это принесло какие-либо улучшения, а время загрузки снизилось примерно до 5,5.

Я полностью озадачен тем, почему более быстрый экземпляр ECC для ECD потребует больше времени для того же самого вызова API, что и экземпляр dev. В итоге я изменил класс prod db с db.t2.large на db.m4.large и получил время отклика 4.0s.

Чувства, подобные «старому» экземпляру DB DB, были вроде изношены/забиты (есть ли такая вещь? Я как-то сомневаюсь в этом ...). Даже более маленький экземпляр dev + dev db ответил гораздо быстрее. Несмотря на то, что различные масштабирования RDS принесли некоторое улучшение, я сомневаюсь, что разница между db.t2.large -> db.m4.large вызовет изменение этой величины.

Возможно, если у кого-то есть идеи, что происходит, я был бы очень рад обсудить это.

Смежные вопросы