Первая статья в нашем блоге. Я постарался написать хороший текст на основе докладов из Failover Bar и прошлого митапа Альфы, и человеческими словами объяснить, зачем нужен GraalVM.

Мы поговорим об экономии на облаках в мире победившего Кубернетиса, обсудим перформанс современного приложения, и какие ништяки Native Image помогут нам во всём этом.

Mad skills bonus: вы сможете писать на Java приложения для командной строки так, что они не будут тормозить по сравнению со скриптами на Bash. Но вначале небольшая телега вместо вступления.

Когда-то, сто лет назад в 2018 году, Леша Федоров зачем-то выпустил меня на JUG в Питере, где я затирал дичь про GraalVM.

Дичь заключалась в том, что, несмотря на множество интересных технических фактов из научных работ и потрясающие ракетные технологии, оставался непонятным вопрос: зачем это все нужно? Наверное, правильный ответ в 2018 году должен был звучать как "низачем, это просто интересно". Сейчас ситуация изменилась.

"Хаос - это не провал, хаос - это лестница" говорил известный персонаж Петир Бейлиш из Игры Престолов. В этом году случилось много не самых приятных вещей, но есть и плюсы - я наконец-то работаю в компании, которая делает свой собственный дистрибутив Java - Axiom JDK, и у нас как у Java-сообщества есть почти свой собственный meetup-бар Failover Bar, где можно на халяву делать JUG и тренировать доклады на Joker и Highload.

Что еще произошло

  • GraalVM стал очень нужен;

  • Kubernetes всех победил;

  • Компании массово мигрируют на российские облака.

Тут надобно бы поговорить о ресурсах, но есть нюанс: на проде о закупке оперативной памяти обычно думают спецпально обученные люди, и для разработчика это не так больно. Поэтому сделаем, чтобы было максимально больно.

Представьте, что вы пилите какой-то пет-проект. Например, вы взяли Spring PetClinic и решили сделать сайт для собачего приюта в родном городе. Кстати, это хорошая идея - вы не представляете, скольким "обычным" людям пригодились бы наши знания в разработке.

Два ядра, четыре гига и 200 гигов диска для нашего хэлловорлда должно быть достаточно. Сходим на Амазон и создадим такую виртуалку.

Теперь сходим в МТС Клауд и создадим такую же виртуалку.

Можно сходить куда-нибудь во flops.ru и получить все то же самое дешевле, но в обмен на стремную скорость дисков. Или в Селектел, или в Сберклауд, или в Яндекс-Облако.

Это не гайд по всем облачным провайдерам России, это важная заметка: облака стоят больших денег. Больше, чем то, к чему мы привыкли раньше.

Если вам кажется, что 8 тысяч - не деньги, и вообще-то один поход в магазин, то вспомните, что "два ядра, четыре гига" - это минимальный сетап. У вас же Java, а не C++ какой-нибудь. Если купить ядра 4, памяти 16, а SSD - 400, то это уже 20 тысяч в месяц.

Хаброкарьера в 2021 году проводила исследование зарплат разработчиков. Возьмем зарплату джава-тимлида, и воспользуемся нехитрой формулой: 20 000 руб / 300 000 руб * 100% = 7% зарплаты тимлида нужно отдать на наш чудесный пет-проект. Дальше разговаривайте со своей жабой.

Моя жаба давно победила, поэтому тесты для пет-проектов я гоняю не в облаках, а на старых компьютерах, стоящих дома в гардеробе. С учетом, что эти компьютеры уже давно куплены, экономия - гигантская.

Можно ли убежать на другие технологии?

Большая проблема в том, что на другие технологии убежать можно, но радости от этого не будет. Будет геморрой.

Например, у нас есть чудесный Rust. Чудесный до того, как ты осознал время компиляции (на больших проектах может оказаться сравнимо с GraalVM Native Image). Или то, что у Rust огромные проблемы с тулингом для поиска проблем. Например, если у тебя потекла память, все что ты можешь сделать - поставить специальный аллокатор, который может быть тебе что-то скажет (или нет). Опять же, поинт не в том, чтобы пнуть Rust, тем более что я сам им пользуюсь и нежно люблю. А в том, что всё имеет свою цену.

Для энтерпрайза уход с джавы имеет последствия в виде потери чудовищных денег, которые уже вложены в легаси. И потеря всего тулинга для джавы, который сильно уменьшает TCO. А для мелкостартапа уход с джавы означает страшные потери времени на разработку - с другими технологиями куда больше нужно мучиться в отладке, настройке, поддержке прода, итп.

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

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

Перформанс облачного приложения

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

Возьмем какую-нибудь условную Jira, которой для запуска нужно ресурсов как на обсчёт полёта до Альфы Центавры, которая дико тормозит и переваливается на старте, но если уж запустилась - работает довольно прилично. Пока не упадет, но это уже другая история.

Этот профиль использования отлично подходит для JIT (just-in-time compiler), и именно поэтому Java когда-то 20 лет назад настолько хорошо выстрелила и зашла корпорациям.

Времена меняются, и теперь у нас повсеместно облака и Kubernetes.

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

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

И нам уж точно не хочется иметь классический массив пред-прогретых JVM, которые "ничего не делают и только топливо жрут", как Скрипач в "Кин-Дза-Дза".

Для этого профиля использования гораздо лучше подходит что-то предварительно скомпилированное. Нам нужен AOT (ahead-of-time compilation).

Размер или смерть

Дальше у нас есть чудесный выбор. Или как-то переписать приложение, чтобы оно начало дичайше AOT-иться, или уменьшить размер базового образа.

Игры с размером базового образа существуют, мы их рассмотрим в конце (тем более, что это шанс попиарить российский Axiom JDK).

Переписать приложение на Go, Rust или C++ заманчиво, но какой же бизнес на это даст денег? А если у тебя свой собсвенный мелкостартап на три человека, задумайся - осилишь ли ты тратить время на утечки памяти и процессора в Go/Rust, согласен ли забиндить if err != nil на тачбар своего макбука (придется купить старый макбук с тачбаром!). Ну и конечно, никто без особой причины не будет трогать С++.

К счастью, у нас есть компромиссный выход в виде GraalVM Native Image.

Что это такое?

Я постараюсь выбросить весь маркетинговый буллшит и ответить как есть. Получится не супер пафосно, но уж как есть. GraalVM - это Java-машина, построенная на основе классической OpenJDK, с набором дополнительных интересных фичей.

Вообще, раньше это было что-то типа игрушки для ученых, которые тестировали на ней свои идеи и писали диссертации, пользуясь красивым и человекопонятным кодом (не как в ad-файлах OpenJDK). Такой свет в темном царстве Java 8, когда люди и не надеялись, что свежие релизы джавы будут выходить два раза в год. Например, GraalVM может на интепретаторах поверх Truffle запускать что угодно, включая Python и C++. Не совсем понятно, зачем это нужно широкому потребителю, но звучит крайне волнующе, как особая граальная магия.

Но в какой-то момент ученые и инженеры понаписали достаточно качественного кода, чтобы он победил JIT C2 в OpenJDK, а это уже реальные деньги и совсем другой бизнес.

Насколько хорош этот JIT? Бенчмарк Ренессанс считает, что там можно выжать процентов 10 легко. Чем сразу же воспользовались всякие Твиттеры и другие большие энтерпрайзы (вспоминаем доклады Криса Талингера). Десять процентов на проде - это классическое "двадцать бабушек - уже рубль". Миллионы баксов.

Но у нас речь не о джите, а о маленькой утилите из стандартного набора, которая называется GraalVM Native Image. Она умеет компилировать JAR-файлы в исполняемые бинарники (например, EXE на Винде).

Идея простая - если мы можем что-то скомпилировать just in time в рантайме, то наверное, мы можем сделать то же самое и заранее, до запуска? Звучит как отличная идея для научной работы :) И вот, результаты всех этих работ у нас в руках.

5 шагов к успеху

  1. Скачать архив (для глобального использования подойдет Community Edition с сайта Oracle, для российских разработок запросите триал Axiom Native Image Kit).

  2. Распаковать: tar -zxf …

  3. export GRAALVM_HOME= …

  4. export PATH=GRAALVM_HOME/bin:PATH

  5. gu install native-image

Последняя команда нужна потому, что дистрибутив GraalVM и так слишком жирный. Если впихнуть туда все на свете, включая Native Image, он начнет весить не 300 мегабайт, а все 3 гигабайта, а этого нам не нужно. Поэтому там есть встроенный пакетный менеджер (прямо как в дистрибутиве GNU/Linux), который позволяет добавить туда дополнительные технологии за одну короткую команду.

Разница в фазах работы

У обычного приложения есть всего одна фаза - рантайм.

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

В случае с Native Image, фазы две: вначале сборка, и потом рантайм.

Фазы эти разнесены во времени и пространстве. Например, сегодня можно скомпилировать ваше гигантское приложение в датацентре, скачав миллиард зависимостей из Maven Central, а уже завтра уехать в отпуск в Сочи и запускать его на маломощном ноутбуке без интернета. Профит.

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

Вначале Native Image собирает все доступные классы. Туда попадают не только классы из твоего приложения и зависимостей, но и стандартная библиотека JDK.

Дальше оно докладывает к этому нечто под названием Substrate VM. Можно думать об этом как о минимальном наборе нативных костылей, чтобы рантайм взлетел. Например, туда входит сборщик мусора и шедулер тредов.

Дальше оно в статическом анализе смотрит, к каким членам классов есть доступ, следует по всем вызовам методов и конструкторам, инициализирует все встреченные классы.

Только с этими найденными классами оно будет работать в дальнейшем, только они попадут в бинарник. Это снижает размер файлов и скорость запуска.

Полученный код компилируется и пишется в секцию TEXT.

Дальше инициализируются структуры данных и классы, сериализуются в Image Heap, и записываются в бинарник, в секцию DATA.

Проблемы инициализации

Во первый, вот вам небольшой рофл. Сдампить при инициализации можно все что угодно, достаточно записать это в поле как статические данные. Веселые картинки, ролики с ютуба, Lord of The Rings в режиссерской версии. После чего вас увезут в дурку, конечно.

Здоровые люди сохраняют туда карренси, локали, дейт-тайм конвертеры и прочий мусор, тормозящий на старте приложение.

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

  • Reflection

  • Proxies,

  • Method Handles

  • Serialization

  • JNI

  • Resources

  • Наверняка что-то еще, что я забыл

Поэтому вам нужно вручную писать конфиги типа:

[
  {
    "name" : "java.lang.String",
    "fields" : [
        { "name" : "value", "allowWrite" : true },
        { "name" : "hash" }
    ],
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] },
      { "name" : "<init>", "parameterTypes" : ["char[]"] },
      { "name" : "charAt" },
      { "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
    ]
  }
]

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

Можно запустить ваше приложение на обычной JVM со специальным джава-агентом, после чего использовать приложение в хвост и гриву некоторое время. Например, отдать реальным пользователям или тестировщикам. Вот как это делается:

$JAVA_HOME/bin/java -agentlib:native-image-agent
=config-output-dir=META-INF/native-image 
-jar App.jar

Сразу после остановки приложения у вас сгенерится целая пачка жирных JSON-ов с конфигами, и писать вручную почти ничего не придется. Ну или такова идея. Дальше можно скомпилировать приложение с этими конфигом, и оно должно скомпилироваться без ошибок: native-image -H:ResourceConfigurationFiles=config.json

Тесты

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

К счастью, для автоматического поиска тестов у нас есть еще один джава-агент, но на этот раз, лежащий во внешнем репозитории Native Build Tools.

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

Когда инициализировать?

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

Вопрос - когда нужно этим заниматься?

Вопрос топовый, потому что раньше поля инициализировались на этапе сборки. А потом перестали, и сейчас вариант по-умолчанию - это рантайм.

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

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

Во-вторых, не все классы по смыслу совместимы с идеей "заморозки" внутри бинарника. Если ты заморозишь Random, он начнет отдавать одни и те же цепочки чисел, и наверное, это не то, что мы от него ждем. А что рантайм должен сделать с сохраненным внутрь бинарника тредом? Не знаете? И никто не знает.

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

Самая жесть в том, что таким образом на обычного разработчика перекладываются проблемы фреймворков. Ты случайно инициализировал у себя часть чужого фреймворка (например, кусок Hibernate), а он не собирается под Native Image с какой-то непонятной ошибкой. Вообще-то это проблема авторов Hibernate, что он не собирается. Но теперь это и твоя лично проблема, решать которую придется дикими костылями.

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

Время сборки

И вот мы подошли к самой большой и страшной проблеме GraalVM Native Image - время сборки.

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

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

Эту проблему давным-давно прочувствовали на себе разработчики C++. Вот чудесный гайд для новичков в проекте Chromium:

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

Быстро эту проблему не решат, но тем не менее, перспективы довольно хорошие. Разработчики активно пилят GraalVM, новые коммиты приходят каждый день. За последнее время скорость сборки GraalVM Native Image увеличилась раза в два, а со временем туда могут добавить кэш компиляции (прямо как в C++) и проблема просто исчезнет.

Для нас же сейчас стоит осознать, что стандартная практика подключения к своему проекту половины Maven Central резко потеряла свою актуальность. Думайте, что подключаете.

Помочь в этом может дашборд, который встроен в GraalVM:

native-image -H:+DashboardAll -H:DashboardDump=dumpfileoversized

Идея очень простая: чтобы бинарник занимал меньше места и быстрее собирался, нужно выбросить весь ненужный код. Какой код не нужен? Об этом скажет Native Image с параметром +DashboardAll.

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

Дополнительное сжатие

Native Image достаточно хорошо сжимает бинарники. Но upx и gzexe сожмут их еще сильнее, минимум на треть.

upx --best -k my_native_image

Настройка сборщика мусора

В Native Image есть собственный сборщик мусора. По сути, стандартный сборщик мусора с поколениями (generational scavenger), вроде того, что был во времена Java 9.

В платной версии GraalVM от Oracle есть еще и G1. В следующем году в GraalVM добавят ParallelGC, он окажется и в Axiom Native Image Kit. У ParallelGC throughput даже лучше, чем в G1, и это куда лучше подходит для облаков. Но надо понимать, что это платные коммерческие решения, а сейчас мы поговорим о том, что есть сразу и на халяву: наш generational scavenger.

У него есть несколько настроек, вот часть из них.

В рантайме:

  • -Xmx – максимальный размер кучи в байтах

  • -Xms – минимальный размер кучи в байтах

  • -Xmn – размер молодого поколения в байтах

  • -XX:+PrintGC – распечатать информацию о сборках мусора

  • -XX:+VerboseGC – можно добавить и увидеть подробности

Во время сборки:

  • -R:MaxHeapSize – максимальный дефолтный хип в байтах

Для любого джависта эти настройки достаточно понятны, но все-таки, пара нюансов. Во-первых, сборщик вылезает за лимиты -Xmx, поэтому ставить размер контейнера в точности как Xmx не стоит. Это происходит из-за особенностей реализации сборщика мусора, когда он перемещает объекты - он их складывает в специальное отдельное место, которое в Xmx не входит. Строго говоря, и обычная JVM-ка тоже занимает больше своего Xmx. Какой вывод для использования в Кубере? Сделайте контейнер чуть больше.

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

Зачем это может быть нужно? Ну например, если вы пишете какую-то свою команду вроде lscp или rm -rf --no-preserve-root /*, и вам хочется иметь минимальное время старта. Все понимают, что на обычной джаве можно написать утилиту cp, но запускаться она будет миллион лет. Можно иметь для этого пул пред-прогретых JVM, и другие извращенские технологии, но с появлением Native Image все это больше не нужно.

Конечно же, такую утилиту командной строки можно написать на каком-то другом нормальном языке, типа Go или Rust. Go еще и компилируется в миллион раз быстрее. Но используя Java, вы можете пошарить код консольного приложения с вашим основным продуктом. Очень удобно писать всякие консольные клиенты для веб-сервисов, они делаются простым переносом структур данных сетевого протокола (какие-то DTO-шки на Java) внутрь консольного приложения.

Уменьшение размера контейнера

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

В первом приближении, можно использовать любой slim образ. Например, Debian Slim или Axiom Runtime Container, с интегрированными внутрь дистрибутивами Java.

Debian Slim бесплатный, но огромный. И еще его иногда мантейнеры собирают неправильно, и он становится совсем огромный (400 мегабайт вместо 200).

Axiom Runtme Container платный, но сильно меньше (около 50-100 мегабайт), и подходит серьезным энтерпрайзам (сейчас на нем работает весь российский банкинг). По факту, Axiom Runtime Container - это самые маленькие контейнеры в мире, на чем они и зарабатывают себе на хлеб с маслом (стоимость лицензии на Axiom рядом не стоит с экономленными на облаках деньгами).

Спускаясь ниже по кроличьей норе, можно использовать сборки без полного дистрибутива Linux. Например, Google Distroless. Для того, чтобы Native Image там заработал, нужно собрать его со специальным флагом: native-image -H:+StaticExecutableWIthDynamicLibC …. Результат будет очень спорный и менее универсальный, чем в случае с Debian Slim и Axiom Runtime Containers, но что не сделаешь ради уменьшения размера образа.

Ну и если вы совсем любитель жесткого BDSM, можно использовать scratch-контейнер. Для этого ничего не нужно качать, нужно написать from:scratch в вашем докерфайле, а Native Image нужно статически собрать с маслом: --static --libc=musl. Результат будет совсем-совсем спорный, но двадцать бабушек - уже рубль.

Боль и удовольствие

Native Image - очень новая, крутая и неоднозначная технология.

С одной стороны, это боль.

  • Возможно, придется переписывать приложение;

  • Специфичные паттерны нагрузки (плоский профиль AOT vs пиковый профиль JIT);

  • Не самый продвинутый сборщик мусора (мы еще добавим ParallelGC в Axiom Native Image Kit, но не сейчас);

  • Молодая технология с возможными багами;

  • Нужно разбираться в куче хитрых конфигов;

  • Нативный код слабо отлаживаем, нужно отлаживать на обычной JVM;

  • IDE не подсвечивает ошибки, связанные с Native Image, и нескоро научится (если вообще);

  • … (список ада можно продолжать).

С другой стороны, это огромное удовольствие.

  • Маленькие бинарники, которые мгновенно запускаются;

  • Счета за облако радуют глаз;

  • Отлично выполняющиеся SLA радуют клиентов (а они вас - своими деньгами);

  • Красивый, понятный, хорошо отрефакторенный код фреймворков;

  • Куча новых современных технологий, с которыми приятно разбираться;

  • Отлично дополняет slim образы (типа Debia Slim или Axiom Runtime Container).

Выбирать или нет GraalVM Native Image прямо сейчас - это большой вопрос, но уже прямо сейчас понятно, что за этой технологией - будущее.

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


  1. maxzh83
    09.12.2022 14:37
    +1

    Есть вопросы с обоснованием зачем это все нужно.

    Времена меняются, и теперь у нас повсеместно облака и Kubernetes.

    И микросервисы. Основная идея которых, быть небольшими и довольно легкими. Да, стартуют они не моментально, но гораздо шустрее монолита. И эта скорость вполне может устраивать. Грубо говоря, пустое приложение на Spring Boot стартует за пару секунд. Потом на него навешают всякие валидации схем БД, накатывание миграций и прочее и стартовать он станет минуту (тут, кстати, вопрос что делать со всем этим в нативном приложении). Но все равно, даже такая цифра для многих сценариев энтерпрайза вполне себе норм.

    Кубер перезапускает приложения

    Да, но если он это делает часто, то зачастую это сигнал о том, что с приложением что-то не то.

    Поэтому нам важна скорость перезапуска и количество используемой оперативной памяти.

    У вас есть информация по использованию памяти? Интересно насколько эффективно это получается у нативного приложения? По идее, размер heap же никак не уменьшается.


    1. olegchir Автор
      09.12.2022 15:25
      +1

      Частично согласен, частично нет. Попробую развернуть

      Если говорить об энтерпрайзах, вопрос не в том, что "достаточно-недостаточно", а что можно сэкономить ещё. Если компания в месяц тратит на облако десять миллионов рублей, то 10% это уже приличная сумма, на которую можно купить нескольких синьоров. В реальности, большие компании тратят сильно больше.

      Представь, вот у тебя какая-то система, состоящая из нескольких сотен микросервисов. (Насколько помню, в Альфе кор занимает что-то в районе трех сотен, слышал на каком-то митапе. А у Нетфликса там под тысячу). И каждый из них обернут в условный Spring Boot, который отжирает память у каждого из микросервисов снова и снова, на одно и то же. Это очень много бесцельно потраченных ресурсов!

      Наверное, можно представить менеджера энтерпрайза, которого не волнуют деньги, в том числе что на его личную премию капнет 10% от сэкономленных 10% (лишняя сотня тысяч в примере выше), но это точно массовый чел?)

      Да, но если он это делает часто, то зачастую это сигнал о том, что с приложением что-то не то.

      А в большой системе у тебя всегда есть что-то, что не работает, и это тратит деньги :)

      А еще у тебя может постоянно меняться скейлинг в зависимости от того, сколько людей набежало (условный Нетфликс, в котором утром нет никого, а под вечер есть кто-то), или когда загрузка меняется между регионами (когда Восток спит, Запад еще смотрит), или еще что-то.

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

      По идее, размер heap же никак не уменьшается.

      Рабочие данные магически, конечно, никуда не ужмутся. Да и производительность сборщика мусора на достаточно больших данных (сотни гигабайт) заставит заплакать. А вот размер приложения может сократиться очень сильно.

      Там в статье очень непросто есть этот раздел про JIT vs AOT.

      Мы эксплицитно говорим, что нам нужна максимальная скорость деплоймента, минимальный размер и атомарность поставки. И взамен мы готовы пожертвовать пиковой производительностью и эффективностью работы с памятью (микросервисы на то и микросервисы, что они не должны тратить много памяти).

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

      Точно так же тебе тогда не нужен Go и Python на продакшене, у них со всем этим тоже так себе дела обстоят.


      1. maxzh83
        09.12.2022 15:43
        +1

        И каждый из них обернут в условный Spring Boot, который отжирает память у каждого из микросервисов снова и снова, на одно и то же. Это очень много бесцельно потраченных ресурсов!

        Поэтому и хотелось узнать информацию, сколько именно выиграем по памяти от использования native. Т.е. какие-то цифры (проценты или мб). Почему условный Spring Boot вдруг перестанет жрать память? Короче, есть подозрение, что это экономия на спичках.

        Рабочие данные магически, конечно, никуда не ужмутся. А вот размер приложения может сократиться очень сильно

        Сильно это насколько?

        Там в статье очень непросто есть этот раздел про JIT vs AOT.

        В статье нашел только околомаркетинговую картинку.

        Наверное, можно представить менеджера энтерпрайза, которого не волнуют деньги, в том числе его личная премия, но это точно массовый чел?)

        Наверное, можно представить менеджера энтерпрайза, которого не волнует, что фичи не вывели вовремя в прод потому, что разрабы пытаются отладить и собрать нативное чудо, но это точно массовый чел? )

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

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


        1. olegchir Автор
          09.12.2022 16:00
          -5

          Как доеду до какого-нибудь места, где есть компьютер - соберу что-нибудь и скажу точные цифры. Сейчас в руках только телефон, под него GraalVM пока, вроде бы, не делают :)

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


          1. maxzh83
            09.12.2022 16:58
            +8

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

            Конечно могу. Если бы писал статью, в которой делал акцент на экономии ресурсов, то так бы и сделал.


  1. akardapolov
    09.12.2022 18:45
    +1

    Воот. Тоже на будущее думали про использование GraalVM. Зависимостей штучки три-четыре. Надемся будет безпроблемная компиляция в native image real-time-intelligence/fbase: Hybrid time-series column storage database engine written in Java (github.com)


    1. olegchir Автор
      09.12.2022 19:45

      Удачи :) Расскажите, если чего получится!


  1. kovserg
    09.12.2022 19:37
    +5

    У вас картинка не полная
    image


  1. crackedmind
    10.12.2022 00:28

    Ну если хотите повышенное использование памяти и увеличенное время старта, то upx самое то


    1. excentro
      11.12.2022 12:14

      Есть какие-то исследования по этому?


  1. SimSonic
    10.12.2022 07:59
    +3

    Axiom Runtme Container платный, но сильно меньше (около 50-100 мегабайт), и подходит серьезным энтерпрайзам (сейчас на нем работает весь российский банкинг).

    Если честно, вообще упустил момент, когда и как этот Axiom появился. Вроде бы был Беллсофт, который делал понятное: коммитил в OpenJDK, собирал свои JDK и docker-образы, двигал Alpine и проник по умолчанию в билдпаки paketo. Что произошло потом? Это какое-то разделение на с этой/с той стороны? С этой стало "только платно"? Если я по собственной невнимательности упустил какие-то статьи / новости, прошу бросить в меня ссылками :)

    Как оно может уже быть во всём российском банкинге, когда банкинг это крайне медленная инерциальная штука? Вот я смотрю на CI раннерах в одном из доступных мне банков, там россыпь образов разного, от maven:3 и openjdk:11, до той же либерики, которую затащил я, но Axiom-а нету. Слишком громогласное заявление, и оттого отталкивающее. Извините :)


    1. therb1
      11.12.2022 21:53

      Вранье это маркетинговое. В Тинькофф точно нет этого axiom.


  1. crazylh
    10.12.2022 16:13

    Кубернейтис и экономия времени старта. На секундочку - пока твой приклад не начнет отдавать хелсчеки - куб в него не будет роутить трафик. То есть выкатываем новую версию, а она (обоже, если там какой-нибудь томкат) стартует целых 80-90 секунд, и, обоже, эти 90 секунд у нас продолжает работать старая версия. Странная метрик, если честно, время старта приложения, наверно для локальной отладки - ок, но в проде, где у тебя почти всё эвенчуально, странная.


    1. Zy2ba
      12.12.2022 11:10

      Зато если вдруг под трафиком новая версия начала погибать, то очень бы хотелось чтоб выкатка предыдущей, уже потушеной, произошла как можно скорее)

      Конечно такое может решаться канарейкой с постепенным переключением трафика, но дерьмо таки случается)