--- aliases: tags: - зрелость/🌱 date: "[[2023-10-21]]" zero-link: - "[[00 Java разработка]]" parents: - "[[Quarkus]]" linked: --- В этой статье представлено сравнение сборок сервиса в режиме JVM и в режиме исполняемого файла. В первой части статьи дана общая теория и сравнение с работой в режиме JVM. А во второй части статьи дана инструкция, как собрать свой сервис в образ с исполняемым файлом. ## Обзорная теория GraalVM позволяет заранее (в AOT-режиме) компилировать программы в нативный исполняемый файл. Это означает, что вы можете компилировать ваш код Java непосредственно в машинно-специфичный код. Получаемая в результате программа не работает на JVM, но использует все необходимые компоненты, в частности, управление памятью, планирование потоков и прочее. Правда, AOT-компиляция серьезно ограничивает динамические возможности Java (загрузка классов во время исполнения, рефлексия, прокси, т.д.). Quarkus использует GraalVM и предоставляет экосистему, поддерживающую AOT-компиляцию во время сборки. ![](hrbgkaa4flnejnwq-qiutmznacu.png) ### Производительность В блоге кваркуса имеется [только одна статья](https://quarkus.io/blog/runtime-performance/), которая посвещена сравнению производительности работы в режиме JVM и в режиме исполняемого файла. Она датирована [2019](2019.md) годом (версия Quarkus 0.18.0), однако пример на GitHub обновлялся в [2021](2021.md) году. При желании [этот пример](https://github.com/johnaohara/quarkusRestCrudDemo) можно актуализировать и [провести новые тесты](https://quarkus.io/guides/performance-measure). > В статье идет сравнение с фреймворком Thorntail. Хотелось бы увидеть сравнение с Spring, но его нет. Однако, нас в статье будет интересовать разницу между сборкой испольняемого файла и работы в режиме JVM ![](throughput.png) Из графика видно, что максимальная пропускная способность, измеренная в запросах в секунду (Req/Sec), в режиме нативной сборки в 2 раза меньше, чем в режиме JVM. Но не спешите делать выводы. При большом количестве одновременных пользователей среднее время отклика для Quarkus в режиме JVM составляет 0,91 мс, а в режиме Native среднее время отклика смещается до 2,43 мс в обмен на более эффективное использование RAM. Время запуска ![](screen.png) Из таблицы видно насколько быстрее стартует исполняемый файл, по сравнению с JVM. В 90 раз быстрее. Максимальное использование RAM ![](c56a3afe-93a6-4c03-b9ac-bb6da1580e2f.png) Максимальное потребление RAM в режиме исполняемого файла почти в 3,5 раза меньше, чем в JVM режиме. ### Выводы Не смотря на то, что сборка в режиме исполняемого файла проигрывает по пропускной способности в 2 раза, мы можем запустить в 3+ раз больше инстансов, чем при работе в режиме JVM, и тем самым получить больше пропускной способности, чем в режиме JVM. Также стоит отметить, что запуск новой реплики занимает миллисекунды. Все эти особенности сборки исполняемого файла позволяют утилизировать ресурсы более эффективно. Например, мы можем оставлять работать одну/две реплики сервиса, когда нагрузка на сервис минимальная, а в случае увеличении нагрузки мы можем быстро добавить новых интсансов. Но стоит помнить, что сборка исполняемых файлов ограничевает в выборе библиотек, которые можно использовать. Если они используют динамические возможности Java, то исполняемый файл попросту не соберется. В общем, для каждого сервиса должен быть индивидуальный подход. Какие-то сервисы имеет смысл собирать в исполняемые файлы, а какие-то оставить на JVM. Также стоит учитывать, что сборка исполняемых файлов - дорогостоящий процесс, требуется минимум 4 процессора и 4 ГБ памяти. И сборка занимает больше времени. Поэтому необходимо достаточное колличество раннеров, чтобы не было длинных очередей на CI/CD. ## Тесты в нашем окружении Пока не проводились масштабные тесты, но некоторые эффекты видны невооруженным взглядом. Сборка исполняемых файлов была опробована на [сервисе операторов коммуникационного сервиса](https://gitlab.t1-consulting.ru/t1-crm/communication/communication-operator-service). Там же можно изучить все детальнее. Для теста был выбран GraphQL эндпойнт, который просто ходит в БД и отдает данные из БД. Никакой сложной обработки, никаких вызовов других сервисов, кроме поиска сессии пользователя в Redis. Замеры производились незамысловатым образом, через Postman. Но как я уже сказал, разница слишком очевидна. Также напомню, что на один под выделяется 512 мб RAM. Сразу отмечу, что запуск пода занял меньше секунды, против 40-60 секунд. 1. Запрос данных в режиме JVM после того, как сервис был поднят в кубере. Занял 10 секунд, потому что многие бины в кваркусе в режиме JVM инициализируются лениво. ![](3%201.png) 2. Повторный запрос данных в режиме JVM. Были запрошены другие данные (другая страница), чтобы избежать использование кэшей, которые возможно делает кваркус. Занял почти 5 секунд. Но эти данные разняться, но в среднем запрос занимает 1-2 секунды. ![](1%201.png) 3. Запрос данных в режиме исполняемого файла сразу после того, как сервис был поднят в кубере. 796 миллисекунд против 10 секунд. Данные были запрошены те же самые, что и в первом тесте. ![](5%201.png) 4. Повторный запрос в режиме исполняемого файла. Занял 132 миллисекунды против 1-2 секнд в режиме JVM. ![](4%201.png) ## Дополнительные материалы * [Сборка Quarkus приложения в исполняемый файл](Сборка%20Quarkus%20приложения%20в%20исполняемый%20файл.md) * [BUILDING A NATIVE EXECUTABLE](https://quarkus.io/guides/building-native-image)