С момента появления серверных сред для JavaScript господствующее положение занимает NodeJS как основная среда выполнения. Сейчас Node вместе с менеджером пакетов NPM широко используется в самых разных случаях — от небольших любительских сайд-проектов до часто посещаемых корпоративных систем. Большинство разработчиков не осмеливаются бросить вызов Node из-за его безусловного (по умолчанию) статуса в индустрии и всеобъемлющей экосистемы разработки с открытым исходным кодом. Первым серьезным соперником был Deno, но он так и не прижился, скорее всего, из-за отсутствия надежной совместимости между Node, пакетами NPM с открытым исходным кодом и CommonJS.
8 сентября 2023 года новый конкурент Bun официально объявил о выходе стабильной версии своей среды выполнения и сопутствующего инструментария, включая NPM-совместимый менеджер пакетов, пакетировщик, API и многое другое. Bun обещает целый ряд преимуществ, в основном связанных с производительностью и удобством для разработчиков, и при этом может похвастаться высокими стандартами функциональной совместимости. Другими словами, Bun выглядит как модернизированная альтернатива приложений на базе Node, обещающая улучшить все, что касается опыта разработки JS-приложений и их производительности.
Как человек, работающий в основном с бессерверными функционально-ориентированными приложениями, я сразу же задался вопросом: все это звучит здорово, но применимо ли на практике? Мой интерес подогревал тот факт, что Lambda оптимизировала свой сервис для NodeJS, а у Bun пока нет официальной поддержки. Чтобы выяснить, насколько конкурентоспособен Bun по сравнению с Node, я разработал набор бенчмарков для тестирования обеих сред выполнения.
Тесты
Проведение типовых эталонных тестов имеет ограничения при сравнении между режимами выполнения и языками и поэтому не может заменить тестирование конкретных случаев использования. Тем не менее я попытался сфокусироваться на нескольких ключевых областях, которые, по моему мнению, могут представлять интерес для разработчиков в контексте выполнения JavaScript в среде бессерверных функций. В результате пришел к следующим трем эталонным тестам, которые учитывают теоретические, реальные и специфические для бессерверных функций аспекты.
Общее время обработки
Bun утверждает, что может обрабатывать логику в 3–4 раза быстрее по сравнению с NodeJS. Хотя многие приложения могут быть более привязаны к вводу-выводу (запросы к базе данных, сетевые вызовы, чтение/запись в файловой системе и т. д.), такое общее улучшение, если оно верно, поможет любой системе, а особенно тем, у которых нагрузка на процессор и память ограничены. Поэтому я хотел проверить, насколько заявления Bun о чистой производительности применимы к бессерверной среде.
Тест: сгенерировать, а затем отсортировать (с помощью встроенного Array.sort) 100 тыс. случайных чисел 10 раз подряд.
CRUD-API
Тестирование нагрузок, требующих интенсивной работы процессора, интересно, но оно, скорее всего, завышает реальные преимущества Bun. Дело в том, что среды, подобные Lambda, очень часто используются для гораздо более простых приложений, которые в большей степени связаны с вводом-выводом (сеть, файловая система и т. д.). Очень распространенным шаблоном является использование шлюза API, Lambda и DynamoDB для создания простых CRUD-API с простыми вычислениями. Я хотел протестировать эти более реальные сценарии, чтобы понять, может ли Bun приносить пользу даже при отсутствии тяжелой логики, связанной с процессором.
Тест: реализовать функцию CRUD Update, которая проверяет входные данные, извлекает объект из DynamoDB, поверхностно объединяет запрошенные модификации с существующим объектом и помещает его обратно в DynamoDB.
Время холодного старта
Бессерверные функции, как правило, страдают от так называемой проблемы “холодного запуска”. Функции AWS Lambda действуют в контейнеризированной среде, причем каждый контейнер, как правило, работает в течение 10–30 минут. Если поступает запрос, а ни один контейнер не запущен и не доступен, Lambda запускает новый контейнер для обработки запроса, и это время запуска и есть время “холодного старта”, о котором идет речь. Официально поддерживаемые среды выполнения, такие как NodeJS, обычно более оптимизированы для быстрого запуска, поэтому необходимо ответить на главный вопрос: как Bun, неоптимизированная среда выполнения, будет вести себя по сравнению с ними?
Тест: функция Hello world с намеренно вызванным холодным стартом.
Конфигурация среды
Для проведения тестов использовалась следующая конфигурация.
- REST API-шлюз — Lambda-функция (время отклика измерялось от API-шлюза до получения полного сквозного охвата).
- 1024 МБ памяти для Lambda-функций.
- Архитектура x86_64 на платформе Amazon Linux 2 для Bun.
- Базовая конфигурация среды выполнения на архитектуре x86_64 для Node.js 18.x.
- Обеспечение параллельных процессов (5), за исключением теста холодного старта.
Результаты теста общего времени обработки
Данный тест выполнялся 1000 раз для каждой среды выполнения, при этом данные о времени отклика были получены на стороне сервера и собраны в CloudWatch и X-Ray.
Node
- Среднее время отклика: 3736 мс.
- Минимальное время отклика: 3391 мс.
- Максимальное время отклика: 4580 мс.
- Время отклика p95: 3989 мс.
- Время отклика p99: 4404 мс.
Bun
- Среднее время отклика: 1836 мс (-50,9%).
- Минимальное время отклика: 1564 мс (-53,9%).
- Максимальное время отклика: 3571 мс (-22,0%).
- Время отклика p95: 2027 мс (-49,2%).
- Время отклика p99: 2117 мс (-51,9%).
Результаты CRUD-теста API
Данный тест выполнялся 1000 раз для каждой среды выполнения, данные о времени отклика были получены на стороне сервера и собраны в CloudWatch и X-Ray.
Node
- Среднее время отклика: 24 мс.
- Минимальное время отклика: 17 мс.
- Максимальное время отклика: 135 мс.
- Время отклика p95: 29 мс.
- Время отклика p99: 51 мс.
Bun
- Среднее время отклика: 25 мс (+4,2%).
- Минимальное время отклика: 16 мс (-5,9%).
- Максимальное время отклика: 157 мс (+16,3%).
- Время отклика p95: 33 мс (+13,8%).
- Время отклика p99: 44 мс (-13,7%).
Результаты теста на время холодного старта
Данный тест проводился только 10 раз для каждой среды выполнения, поскольку не существует удобного способа автоматизировать принудительный холодный старт в нагрузочном тестировании. Обратите внимание, что результаты здесь отражают полное время отклика, а не только время инициализации.
Node
- Среднее время отклика: 302 мс.
- Минимальное время отклика: 252 мс.
- Максимальное время отклика: 361 мс.
- Время отклика p95: 312 мс.
- Время отклика p99: 361 мс.
Bun
- Среднее время отклика: 775 мс (+156%).
- Минимальное время отклика: 717 мс (+184%).
- Максимальное время отклика: 1382 мс (+282%).
- Время отклика p95: 1174 мс (+276%).
- Время отклика p99: 1382 мс (+282%).
Интерпретация результатов
К моему удивлению, Bun удалось значительно опередить Node в выполнении задач, требующих интенсивной работы процессора. И это несмотря на то что Node был специально оптимизирован для Lambda, а официальный Bun Lambda Layer был недостаточно протестирован (и, следовательно, не оптимизирован). В более простых задачах, связанных с вводом-выводом, Bun также не уступает Node. Это означает, что вы как минимум вряд ли заметите снижение производительности при использовании Bun.
Надеюсь, когда AWS Lambda добавит Bun к официальным средам выполнения, Bun будет работать если не гораздо лучше, то хотя бы немного эффективнее, чем Node. Если же вы выполняете логику с помощью таких фреймворков и библиотек, как Express, Nest, Apollo и NextJS, то, скорее всего, получите стабильно более высокую производительность (это то, что я хотел бы проверить в следующий раз).
Готов ли Bun к использованию в производстве? Я бы не стал пока делать поспешных выводов, поскольку в процессе работы с Bun Lambda Layer столкнулся с некоторыми трудностями, и на данный момент он еще не прошел “боевого крещения”. Кроме того, некоторые API NodeJS еще не совместимы с Bun, да и сам Bun еще новичок, а значит, мы пока не знаем всех его особенностей. Однако все эти проблемы решаемы, и данные тесты показывают: при определенных усилиях у Bun есть реальный потенциал, чтобы стать законной средой выполнения для разработки производственных бессерверных приложений.
Преодоление ограничений холодного старта
Очевидно, что о проблеме холодного старта следует знать, но я бы не стал называть ее решающей, поскольку существует два популярных решения, позволяющих сделать ее практически неактуальной.
- Официальное решение AWS — обеспечение параллельных процессов, при котором заданное количество контейнеров постоянно поддерживается в “теплом” состоянии за определенную плату. В зависимости от нагрузки на приложение это может быть как довольно рациональным, так и более дорогостоящим подходом.
- “Теплое” пользовательское решение, время от времени вызывающее Lambda-функцию, чтобы обеспечить постоянную доступность определенного количества контейнеров. Это хорошее решение для приложений с низкой пропускной способностью, поскольку оно дешево и позволяет держать контейнер готовым к использованию в любой момент, когда это необходимо.
Преимущества производительности
В распространенных случаях использования приложений простая замена NodeJS на Bun может немного улучшить время отклика, придав приложениям более высокую скорость работы. Чем больше приложение будет работать с вычислениями, тем больше будет эффект, поэтому все зависит от конкретных задач. Тем не менее в сочетании со стратегией холодного старта похоже, что Bun будет по крайней мере на одном уровне с Node, предоставляя дополнительные преимущества при разработке.
Экономические преимущества
Сокращение времени выполнения Lambda-функций оказывает прямое положительное влияние на стоимость работы приложения, поскольку Lambda-функции тарифицируются за миллисекунду использования. Если в приложении много логики, связанной с процессором, то переход на Bun может дать значительную экономию средств. Но если приложение выполняет только простые функции, вызывающие базы данных или другие API, и не делает ничего особенного с этими данными в самой Lambda-функции, то, скорее всего, большой разницы не будет, к тому же придется платить за выбранную стратегию холодного старта.
Еще одно предостережение: использование встроенной среды выполнения Node означает, что вам не придется оплачивать инициализацию (холодный старт). С другой стороны, за инициализацию Lambda-контейнеров для Bun взимается плата, поэтому, помимо преодоления проблем холодного старта из соображений производительности, вы, скорее всего, захотите сократить их и из соображений экономии.
Заключительные размышления
Я весьма скептически относился к Bun в целом и особенно к возможностям его использования в бессерверных приложениях. Результаты тестов приятно удивили меня, развеяв мой скептицизм. Хотя лично я пока не собираюсь использовать Bun для Lambda-разработки в силу его новизны, но буду внимательно следить за ним по мере развития экосистемы и дальнейшего совершенствования и оптимизации инструментария для Bun на Lambda.
В этой статье не обсуждался опыт разработки Bun, но вкратце можно сказать, что он порадовал чрезвычайно быстрой установкой и сборкой пакетов, а также эффективной поддержкой локального тестирования Lambda-функций, что в противном случае более болезненно и требует дополнительных инструментов от AWS SAM. Думаю, что в процессе развития Bun получит реальные шансы обогнать Node как основной среды выполнения для разработки на JavaScript и TypeScript. Лично я буду за это болеть.
Читайте также:
- Является ли Bun следующим значительным достижением после WebPack?
- Управление Node.js 19 и NPM 9 с помощью NVM
- 5 советов по оптимизации производительности приложения NodeJS
Читайте нас в Telegram, VK и Дзен
Перевод статьи Mitchell Kossoris: Serverless Bun vs Node: Benchmarking on AWS Lambda