Недавно, 6 мая этого года, в OpenJDK вошёл JEP 425, который добавит к Java 19 в качестве превью-фичи Виртуальные треды.
Пожалуй, этот JEP — самое большое изменение семантики языка после появления Дженериков. Его масштабы трудно оценить. Для начала попробуем прикинуть, как оно может отразиться на нашей зарплате.
Рынок труда JDK экосистемы
Посмотрим на график ниже. Он основан на данных опросов сайта StackOverflow за 2020 и 2021 годы с количеством участников в 34 и 47 тысяч соответственно. Отображены данные, которые касаются языков экосистемы JDK: Clojure, Scala, Kotlin и Java; а также языков C# и F# экосистемы DotNet.
По оси X отложен процент участников опроса, который указал конкретный язык программирования (ЯП) в качестве основного. За точку отсчёта в 100% взяты джависты.
По оси Y отложена медиана годовой зарплаты. Это данные глобального мирового рынка труда, а не какой-то отдельной страны. Точнее, той его части, которая оказалась представлена в опросе на StackOverflow.
Тенденция понятна. Слева и вверху графика расположились языки программирования, позиции которых оплачиваются хорошо, но такую работу трудно найти. Справа и внизу находятся языки, которые оплачиваются похуже, зато они лидируют на рынке труда. Тренд иллюстрируют кривые для результатов 2020 и 2021 годов в JDK. Данные C# и F# добавлены на график для сравнения.
Чем вызван такой разрыв в зарплатах? Да, программировать на Clojure и Scala сложнее, чем на Java. Однако не всегда сложность монетизируется. Теоретические исследования редко финансируются бизнесом. Какую практическую пользу приносит разработка на Scala и Clojure, что её ценят так высоко?
«Точка Нетфликса»
Готовность бизнеса оплачивать головоломную работу объясняет концепция «Точки Нетфликса» за авторством Томаша Нуркевича. Томаш разделяет серверное программирование на два сорта: блокирующее и реактивное. О содержании терминов и чем одно отличается от другого мы поговорим позже.
Стоимость разработки и эксплуатации распадается в представлении Нуркевича на три составляющие: разработка, железо и поддержка. Затраты меняются при развитии: этап малой компании, стартап или большое предприятие. Для малой компании блокирующий подход в целом может стоить где-то вчетверо дешевле реактивного. А по мере роста предприятия стоимость разработки блокирующим способом увеличивается быстрее, чем реактивная альтернатива.
В какой-то момент на этапе зрелого стартапа («точка Нетфликса» в терминологии Нуркевича) реактивный подход становится дешевле блокирующего. Далее затратная на старте технология экономит средства компании.
Обратим внимание на соотношение между составляющими внутри выбранного подхода. В случае блокирующего приложения стоимость железа совпадает с ценой разработки, а стоимость поддержки много меньше этой пары. Разбираться с ошибками и отлаживать блокирующий код легко. Вместе с тем в проекте быстро накапливается сложность. Блокирующий код плохо приспособлен для композиции по фичам и масштабирования.
В случае реактивного приложения стоимость поддержки сравнима с ценой разработки. При рефакторинге кода легко ошибиться. Стектрейсы недоступны. Разобраться с ошибкой бывает очень непросто. Цена железа составляет меньшую долю стоимости и почти не меняется. Код хорошо масштабируется.
Авторская ремарка
Обратите внимание на различие в стоимости железа между подходами в правой части графика. Там двадцатикратная разница! Может быть Томаш Нуркевич её преувеличил?
Мой практический опыт подтверждает оценки Томаша. Разница в стоимости на графике эквивалентна разнице в пропускной способности для одной и той же конфигурации железа. Группа разработчиков «Юзтеха» (и я в их числе) создаёт следующую версию российской Системы межведомственного электронного взаимодействия (СМЭВ). Про нынешнюю версию (СМЭВ-3) уже много написано на Хабре. На предварительных испытаниях наша версия показывает производительность на два порядка большую, чем СМЭВ-3. Нуркевич не обманывает.
Заключительные тезисы
Рассмотренные материалы приводят нас к выводам, которые я изложу здесь в виде тезисов и раскрою в продолжении этой заметки.
Неадекватность мейнстрима
Обычные в нашей отрасли языки серверного программирования — неадекватные. Созданный с помощью языка продукт либо неэффективно работает с железом, либо невероятно сложен в разработке и поддержке. Как мы дошли до жизни такой и как Loom обещает это изменить — увидим в продолжении.
Слоган Loom
Проект Loom выдвигает лозунг «Программировать как будто синхронно, работать как будто асинхронно» (Code like sync, work like async). Экономический аспект лозунга в том, чтобы объединить все сильные стороны блокирующего и реактивного подхода: дешёвую разработку и поддержку, высокоэффективное использование железа, возможность масштабирования. Достижимы ли эти обещания? Посмотрим в продолжении.
Какое мне дело до Loom?
Если вы работаете в старой доброй Java блокирующим способом, то у вас есть хороший шанс повысить с помощью новой фичи эффективность работы с железом. Конечно, от этого ваша компания не превратится в Нетфликс, но расходы на железо сократит. А это неплохой повод для повышения зарплаты.
Чем Loom может помочь экосистеме JDK в целом, тоже разберём в продолжении заметки.
Комментарии (17)
panzerfaust
28.05.2022 11:06+1Не понимаю экзальтации по поводу этого лума. Будет еще одно средство реализации асинхронного/реактивного подхода. Все приложения резко станут модными реактивными? Ну да, так же как они "стали" модными реактивными с выходом реактора или корутин колина
Отрасли по прежнему критически не хватает гуру реактивной парадигмы, а также практики миграции с классики на неблокирующий подход. Писать хеллоуворлды на реакторе это одно, а мигрировать работающий сервис хотя бы на 20к строк - совсем другое.
maxzh83
28.05.2022 12:45+2Все приложения резко станут модными реактивными?
Не станут. Loom вообще не про реактивность, а про неблокируемость, а это не совсем одно и то же. И основная его фича в том, что уже существующий код может стать неблокирующим. И вот это по настоящему интересно. Ну и сложность разработки будет ниже чем в том же реакторе. По сути, пишешь синхронный код
sergey-gornostaev
29.05.2022 12:09Неблокируемость обеспечивается и обычными потоками, а также мультиплексированием. Loom про легковесность потоков и упрощение кооперативной многозадачности. Реактор, акторы и прочие никуда не денутся - это абстракции более высокого уровня, которые просто переедут с пулов на виртуальные потоки.
maxzh83
29.05.2022 16:36+1Неблокируемость обеспечивается и обычными потоками
И да и нет. Внутри традиционного потока код блокирующий (кроме того, локи, synchronized и прочее). Кол-во физических потоков ограничено + переключение между ними небыстрое. Если код неблокирующий, то и большое кол-во потоков не требуется. В эту сторону пошли реактивщики. А loom, да, позволяет исполнять код на виртуальных потоках так, как будто это реальные потоки. При этом в теории никаких телодвижений не требуется и код, написанный под обычные потоки, начнет работать на виртуальных, что сильно повысит масштабируемость.
a_a_vasiljev Автор
29.05.2022 19:34+2Здесь нас (разработчиков) ждёт несколько сюрпризов.
maxzh83
30.05.2022 10:01Это понятно, поэтому и написал "в теории". Но, возможно со временем с развитием этой темы, этих сюрпризов станет поменьше.
snuk182
30.05.2022 00:22+1уже существующий код может стать неблокирующим
Если почитать описание изменений API, то видно, что это не совсем так - там куча мелочей, которые изменены в реализации, от тихого игнора вызова до падения. Учитывая, что игры с потоками присущи как раз application-серверам, рискну предположить, что их как прослойку между VM и бизнес-кодом придется очень плотно переписывать.
nin-jin
29.05.2022 01:53Что такое "реактивная парадигма"?
a_a_vasiljev Автор
29.05.2022 03:13-1Об этом -- в следующей части заметки.
nin-jin
29.05.2022 09:19+1Что ж, надеюсь вы там подробно раскроете разницу между dataflow, controlflow, continuation и eventstream.
a_a_vasiljev Автор
29.05.2022 12:20Про это -- только в 73-й части заметки. Только, боюсь, здесь её опубликовать не получится. Меня и так на Хабре уже почти забанили.
pin2t
29.05.2022 07:55-4Loom устарел, он был более актуален 15 лет назад, чем сейчас. Вон в соседней новости пишут про 128 ядерный серверный процессор. На современном железе, которое в настоящее время только 64-битное, на современной JVM легко создается несколько сотен тысяч обычных системных потоков.
Ну может в Loom будет быстрее переключение между потоками, это безусловно плюс, но настолько ли он большой сейчас.
nin-jin
29.05.2022 09:25Системные потоки кушают много памяти на стек, медленно переключаются и делают это в недетерминированные моменты времени. Поэтому куда эффективнее запускать по воркеру на ядро, каждый из которых пережёвывает легковесные потоки.
a_a_vasiljev Автор
29.05.2022 12:04Всё так. Сергей Куксенко измерял выигрыш в производительности и получал 2-3 раза, если не ошибаюсь. Иван Углянский тоже что-то намерил и расскажет на JPoint (рекомендую). Кто-то говорит о порядках в отдельных сценариях. Всё может быть.
Вместе с этим кричащая разница -- это максимально возможное количество тредов, системных и виртуальных. Она оценивается в два с половиной порядка. Триста раз. То есть виртуальных потоков можно создать даже не сотни тысяч, как предполагал уважаемый pin2t, а миллионы. И JVM потянет.
a_a_vasiljev Автор
29.05.2022 11:18Нуркевич в своём докладе (ссылка в заметке) упоминает, что "решил проблему 10k соединений". Про что это? Блокирующий (классический) подход предполагает, что каждое новое установленное соединение обрабатывается одним тредом. А в JDK жёсткое соответствие: один JDK-тред привязан одному системному треду, и наоборот. Вот 10k системных тредов уже слишком много для ОС. Приложение падает. Скорее всего, оно упадёт ещё до 10k. Поэтому несколько сотен тысяч -- вряд ли.
Проблема 10k решается неблокирующим подходом. Сейчас таких в мейнстриме два: реактивный и легковесные треды (фибры, горутины, корутины, виртуальные треды и так далее).
sshikov
29.05.2022 19:26Ну не совсем. Порядка 10к java threads я создавал на достаточно обычной серверной машине с 64 гигабайтами и 48 ядрами. Вовсе не обязательно приложение будет падать, но с настройкой наверняка придется повозиться. Скорее всего, вылезет какой-нибудь лимит ОС типа числа открытых файлов, придется потюнить GC, ну и т.п. Насколько я помню, 100к threads просто на стек сожрет дохрена памяти (порядка 10 гигабайт, если мне память не изменяет). Так что утверждение про «легко 100к» больше похоже на байки.
И в любом случае, планировщик на 10к процессов, и планировщик на 500к процессов — это две большие разницы, независимо от того, легковесные они сами, или нет.
snuk182
Текстом можно напоить несколько африканских деревень. Обычные зеленые потоки.