Сегодня я хочу выяснить, готов ли Project Loom заменить Spring WebFlux при создании высоконагруженных приложений с высокой пропускной способностью. WebFlux -  замечательная технология с фантастической производительностью, однако: При использовании реактивного подхода код сложнее писать и сопровождать Стектрейсы малополезны при разборе ошибок Все связанные клиенты/библиотеки также должны быть написаны в реактивном стиле В статусе превью-фичи с Java 19, разработка стартовала в 2017 Основное нововведение - виртуальные потоки, призванные значительно снизить трудозатраты на написание и сопровождение приложений Предполагается возможность создавать миллионы виртуальных потоков. (Прим. пер. мне кажется, автору стоило явно выделить основную мысль. Поскольку виртуальные потоки дешевы, то запуск блокирующего кода в таком потоке тоже дешевая операция, т.к. реальный системный поток при этом не блокируется и может заняться чем-нибудь полезным ) Предполагается минимальное вмешательство в существующий код Стек виртуальных потоков хранится в хипе JVM Есть мнение, что Project Loom способен решить проблемы применения реактивной парадигмы. Но что насчет производительности? Мы собираемся проверить производительность сервиса, выполняющего задачу проксирования запроса к некоему третьему сервису, который возвращает ответ с задержкой в 500мс. Исходный код здесь. Мы проверим 3 реализации одного и того же сервиса: Spring Boot (Tomcat) + Project Loom Spring Webflux Spring Webflux + Project Loom Железо: Все тесты крутятся на серверах AWS EC2 Подопытный сервис крутится на ноде t2.micro (1 CPU, 1 GB) Третий сервис крутится на ноде t2.medium (2 CPU, 4GB) Нагрузка создается еще одним внешним EC2 Необходимо кастомизировать настройки Spring Boot, чтобы Tomcat использовал виртуальные потоки вместо своего стандартного пула. Далее используем обычный контроллер Spring MVC Для реализации с WebFlux я использую http-клиент WebClient. Стандартные настройки немного изменены для поддержки большого числа подключений. Здесь мы будем вызывать некий блокирующий код, но на пуле виртуальных потоков Executors.newVirtualThreadPerTaskExecutor(). Результат вызова блокирующего кода оборачиваем в Mono. Tomcat + Loom показывает неудовлетворительные результаты (Прим. пер. Есть основания полагать, что "просто" Tomcat не показал бы даже и таких). Пропускная способность невысока из-за высокой активности GC (~50CPU). Связка Tomcat + Loom неспособна справиться с нагрузкой в 4k параллельных запросов, а для 8k запросов уже происходит OOM. После анализа heap dump ясно, что почти вся память занята инстансами  SocketWrapper, созданными Tomcat. Это легко объяснить, т.к. дизайн Tomcat предполагает модель "1 запрос - 1 поток".  Поэтому обертки сокетов слишком "тяжелы", и использование их в связке с виртуальными потокам неэффективно. Сравним профили WebFlux и WebFlux + Loom Профили нагрузки на память, CPU и GC похожи. Поэтому мы наблюдаем похожую пропускную способность, хотя для 10k запрос Loom даже вырывается вперед. Project Loom это "game changer". Мы показали, что виртуальные потоки эффективны, и позволяют писать простой привычный блокирующий код, который может быть столь же , как код реактивный/неблокирующий. Это означает, что мы сможем легко мигрировать наш блокирующий код на Loom и продолжать использовать код нереактивных библиотек типа Hibernate. Но мы все еще нуждаемся в связке Netty+WebFlux в качестве обертки для блокирующего кода, т.к. Tomcat пока что by-design не подходит для этой задачи. Системный поток все же может быть заблокирован, если внутри виртуального потока есть: Вызовы нативного кода Синхронизированный участок кода/метод. Решение: использовать ReentrantLock и -Djdk.tracePinnedThreads=full Это означает, что существующие библиотека должны быть отрефакторены с заменой ключевого слова synchronized на ReentrantLock. См. https://github.com/pgjdbc/pgjdbc/issues/1951