9.0 KiB
aliases | tags | date | zero-link | parents | linked | |||
---|---|---|---|---|---|---|---|---|
|
2023-10-21 |
|
|
В этой статье представлено сравнение сборок сервиса в режиме JVM и в режиме исполняемого файла. В первой части статьи дана общая теория и сравнение с работой в режиме JVM. А во второй части статьи дана инструкция, как собрать свой сервис в образ с исполняемым файлом.
Обзорная теория
GraalVM позволяет заранее (в AOT-режиме) компилировать программы в нативный исполняемый файл. Это означает, что вы можете компилировать ваш код Java непосредственно в машинно-специфичный код. Получаемая в результате программа не работает на JVM, но использует все необходимые компоненты, в частности, управление памятью, планирование потоков и прочее.
Правда, AOT-компиляция серьезно ограничивает динамические возможности Java (загрузка классов во время исполнения, рефлексия, прокси, т.д.). Quarkus использует GraalVM и предоставляет экосистему, поддерживающую AOT-компиляцию во время сборки.
Производительность
В блоге кваркуса имеется только одна статья, которая посвещена сравнению производительности работы в режиме JVM и в режиме исполняемого файла. Она датирована 2019 годом (версия Quarkus 0.18.0), однако пример на GitHub обновлялся в 2021 году. При желании этот пример можно актуализировать и провести новые тесты.
В статье идет сравнение с фреймворком Thorntail. Хотелось бы увидеть сравнение с Spring, но его нет. Однако, нас в статье будет интересовать разницу между сборкой испольняемого файла и работы в режиме JVM
Из графика видно, что максимальная пропускная способность, измеренная в запросах в секунду (Req/Sec), в режиме нативной сборки в 2 раза меньше, чем в режиме JVM. Но не спешите делать выводы.
При большом количестве одновременных пользователей среднее время отклика для Quarkus в режиме JVM составляет 0,91 мс, а в режиме Native среднее время отклика смещается до 2,43 мс в обмен на более эффективное использование RAM.
Время запуска Из таблицы видно насколько быстрее стартует исполняемый файл, по сравнению с JVM. В 90 раз быстрее.
Максимальное использование RAM Максимальное потребление RAM в режиме исполняемого файла почти в 3,5 раза меньше, чем в JVM режиме.
Выводы
Не смотря на то, что сборка в режиме исполняемого файла проигрывает по пропускной способности в 2 раза, мы можем запустить в 3+ раз больше инстансов, чем при работе в режиме JVM, и тем самым получить больше пропускной способности, чем в режиме JVM. Также стоит отметить, что запуск новой реплики занимает миллисекунды.
Все эти особенности сборки исполняемого файла позволяют утилизировать ресурсы более эффективно. Например, мы можем оставлять работать одну/две реплики сервиса, когда нагрузка на сервис минимальная, а в случае увеличении нагрузки мы можем быстро добавить новых интсансов.
Но стоит помнить, что сборка исполняемых файлов ограничевает в выборе библиотек, которые можно использовать. Если они используют динамические возможности Java, то исполняемый файл попросту не соберется.
В общем, для каждого сервиса должен быть индивидуальный подход. Какие-то сервисы имеет смысл собирать в исполняемые файлы, а какие-то оставить на JVM.
Также стоит учитывать, что сборка исполняемых файлов - дорогостоящий процесс, требуется минимум 4 процессора и 4 ГБ памяти. И сборка занимает больше времени. Поэтому необходимо достаточное колличество раннеров, чтобы не было длинных очередей на CI/CD.
Тесты в нашем окружении
Пока не проводились масштабные тесты, но некоторые эффекты видны невооруженным взглядом.
Сборка исполняемых файлов была опробована на сервисе операторов коммуникационного сервиса. Там же можно изучить все детальнее.
Для теста был выбран GraphQL эндпойнт, который просто ходит в БД и отдает данные из БД. Никакой сложной обработки, никаких вызовов других сервисов, кроме поиска сессии пользователя в Redis.
Замеры производились незамысловатым образом, через Postman. Но как я уже сказал, разница слишком очевидна. Также напомню, что на один под выделяется 512 мб RAM.
Сразу отмечу, что запуск пода занял меньше секунды, против 40-60 секунд.
- Запрос данных в режиме JVM после того, как сервис был поднят в кубере. Занял 10 секунд, потому что многие бины в кваркусе в режиме JVM инициализируются лениво.
2. Повторный запрос данных в режиме JVM. Были запрошены другие данные (другая страница), чтобы избежать использование кэшей, которые возможно делает кваркус. Занял почти 5 секунд. Но эти данные разняться, но в среднем запрос занимает 1-2 секунды.
3. Запрос данных в режиме исполняемого файла сразу после того, как сервис был поднят в кубере. 796 миллисекунд против 10 секунд. Данные были запрошены те же самые, что и в первом тесте.
4. Повторный запрос в режиме исполняемого файла. Занял 132 миллисекунды против 1-2 секнд в режиме JVM.