PlayFramework: kinerja deserialisasi json yang buruk

Infrastruktur dan pembukaan

Saya memiliki Aplikasi PlayFramework (2.3.8) yang dihosting pada instans AWS EC2. Saya memiliki serangkaian objek kompleks, yang harus dikembalikan sebagai string JSON melalui API web. Saya memerlukan salinan array yang dalam, dengan semua objek anak dimuat penuh hingga lapisan terakhir. Array memiliki ukuran 30-100 entri, setiap entri memiliki sekitar 1-10 entri, setiap entri memiliki hingga 100 properti, pada akhirnya tidak ada BLOB atau sejenisnya yang terlibat, semuanya bermuara pada string/ganda/ int/bool. Saya tidak yakin seberapa penting struktur data yang sebenarnya, beri tahu saya jika Anda memerlukan detail lebih lanjut. Ukuran file .json yang dihasilkan sekitar 1 MB.

Kinerja deserialisasi array ini sangat buruk, untuk ~1 MB di mesin lokal saya diperlukan waktu 3-5 menit; pada EC2 dibutuhkan sekitar 20-30 detik.

Masalah awal: kinerja buruk saat menggunakan play.libs json

Array objek saya dimuat dan disimpan sebagai JsonNode. JsonNode ini kemudian diteruskan ke ObjectMapper, yang akhirnya menulisnya dengan PrettyPrinted:

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

Jadi saya menetapkan pelakunya adalah deserialisasi Json.toJson. Sejauh yang saya tahu, ini adalah perpustakaan Jackson yang digunakan oleh PlayFramework.

Meskipun saya telah membaca tentang beberapa masalah kinerja deserialisasi JSON, saya tidak yakin apakah kita harus membicarakan beberapa ratus milidetik hingga detik, dan bukan menit. Bagaimanapun, saya mencoba mengimplementasikan beberapa perpustakaan JSON lainnya (GSON, argonaut, flexjson), yang tidak berjalan mulus.

GSON

Saya "hanya" mencoba mengganti perpustakaan play-json dengan perpustakaan GSON, seperti yang saya lakukan pada bagian kecil proyek lainnya. Ini berfungsi dengan baik di sana, tetapi meskipun saya TIDAK memiliki referensi melingkar, itu melemparkan StackOverflowErrors ke wajah saya, bahkan jika saya mencoba melakukan deserialisasi objek kecil yang dibuat secara manual. Baik di mesin dev saya maupun di instans EC2.

FlexJson

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

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

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

Sejauh ini berfungsi cukup baik, hanya membutuhkan sekitar 20% waktu dibandingkan dengan metode JSON.toJson di atas. Namun, hal ini mungkin terjadi karena ia tidak benar-benar menyalin objek secara mendalam. Itu menyalinnya secara mendalam pada lapisan pertama, namun karena model saya memiliki beberapa properti yang lebih kompleks (dengan anak, cucu, dan cucu...), dan cukup banyak, saya tidak yakin bagaimana melanjutkannya di sini.

Berikut adalah contoh output dari salah satu objek bersarang saya (ini adalah salah satu properti dari objek "atas"):

 "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

Apakah Anda punya saran solusi lain, atau petunjuk apa yang mungkin rusak? Saya juga memikirkan bahwa mungkin entitas hanya dimuat SEPENUHNYA setelah saya memanggil .toJson()? Tetap saja itu tidak akan memakan waktu sebanyak itu.

Terima kasih sebelumnya!


person konrad_pe    schedule 09.11.2016    source sumber
comment
Ada kemungkinan bahwa entitas hanya dimuat SEPENUHNYA setelah saya memanggil .toJson(). Seperti yang saya lihat, Anda menggunakan Ebean. Coba aktifkan logging untuk pernyataan sql untuk melihat apakah grafik objek sedang dimuat sebelum atau selama panggilan toJson.   -  person marcospereira    schedule 09.11.2016
comment
Anda juga dapat mencoba menggunakan sesuatu seperti VisualVM atau YourKit untuk mencari tahu apa yang membuatnya lambat, atau menyimpan file json yang dihasilkan. memuatnya ke memori (menguraikannya) dan kemudian merendernya dengan toJson (jika proses ini lebih cepat dari sebelumnya maka jelas ada sesuatu selain penguraian/pembuatan json yang membuatnya lebih lambat)   -  person Salem    schedule 10.11.2016
comment
Terima kasih atas tipnya dengan logging sql, ide bagus. Ternyata entitas dan entitas turunannya hanya dimuat selama Json.toJson(). Saya mencoba mengubah ambil = FetchType.Eager untuk entitas anak tersebut, tetapi ini tidak ada bedanya sama sekali. Dan pada dasarnya saya tidak begitu melihat perbedaan apa yang akan terjadi, pada titik mana entitas dimuat, jadi mengapa tidak selama keluaran untuk .toJson(). Tapi sepertinya hambatannya ada pada entitas/DB, bukan pada serialisasi JSON..?   -  person konrad_pe    schedule 10.11.2016


Jawaban (1)


TLDR: masalah ini tidak ada hubungannya dengan kinerja deserialisasi JSON PlayFrameworks, melainkan dengan beberapa masalah eBean/database. Mengaktifkan login SQL di application.conf mengarahkan saya ke hal ini.


Komentar dan pemikiran lebih lanjut: Berkat petunjuk marcospereira di komentar, saya menetapkan masalahnya sebagai masalah pengambilan dalam play / ebeans, bukan masalah kinerja JSON.

Jelas sekali entitas saya dimuat dengan malas (/flat) pada awalnya, dengan mengaktifkan logging SQL saya dapat melihat bahwa SELECT yang disiapkan dengan benar hanya diaktifkan setelah kode saya mencapai .toJson(). Begitu banyak objek anak yang hanya diambil dari database saat memanggil .toJson(), yang menghasilkan beberapa ratus SELECT dan oleh karena itu cukup lama untuk diselesaikan.

Bermain-main sedikit dengan skala instance RDS saya menemukan beberapa hasil yang sangat aneh. Ini tidak BENAR-BENAR terkait dengan pertanyaan awal yang diajukan, namun saya ingin berbagi temuan saya, mungkin bisa membantu seseorang di luar sana. Baca tentang itu di bagian di bawah ini.

Eksperimen penskalaan RDS...

Di lingkungan dev saya (t1.micro), saya menghubungkan salinan instance dari prod DB saya pada instance RDS kecil (db.t2.micro), untuk melihat apakah ada perubahan.

Lingkungan prod saya (t2.large) + prod RDS (db.t2.large) membutuhkan waktu sekitar 19,5 detik untuk menyelesaikan panggilan API. Lingkungan dev BARU (t1.micro + db.t2.micro), yang lebih lemah baik dalam komputasi maupun db, hanya membutuhkan waktu sekitar 10,5 detik, yang sangat tidak meyakinkan, karena pada dasarnya kedua instance menjalankan kode yang sama, hanya menunjuk ke server DB lain (dengan konten db yang identik). Saya mengganti dev DB ke db.m4.large untuk melihat apakah ada perbaikan, dan waktu muat turun menjadi sekitar 5,5 detik.

Saya benar-benar bingung mengapa instance prod EC2 yang lebih cepat memerlukan lebih banyak waktu untuk panggilan API yang persis sama daripada instance dev. Pada akhirnya saya mengubah kelas prod db saya dari db.t2.large menjadi db.m4.large dan sekarang memiliki waktu respons 4,0 detik.

Terasa seperti instans DB prod "lama" agak usang/tersumbat (apakah ada hal seperti itu? Entah bagaimana saya meragukannya...). Bahkan instance dev yang lebih kecil + dev db merespons lebih cepat. Meskipun penskalaan RDS yang berbeda membawa beberapa perbaikan, saya ragu bahwa perbedaan antara db.t2.large -> db.m4.large akan menyebabkan perubahan sebesar itu.

Mungkin jika ada yang tahu apa yang terjadi, saya akan sangat senang membicarakan hal ini.

person konrad_pe    schedule 10.11.2016