Головна / Статті

Скільки запитів насправді може обробити сервер Node.js?

Скільки запитів насправді може обробити сервер Node.js? Тести навантаження Express на Heroku та AWS порівняно з Django, оцінка пропускної здатності у 40 тис. запитів на секунду та те, що уповільнює API.

Видавець: REACTAPP.TOP6 хв читання1358 слів

Джерела: How Many Requests Can a Real-World Node.js Server-Side App Handle?

Кожен, хто тільки починає працювати з Node.js, рано чи пізно ставить одне й те саме практичне запитання: скільки запитів може обробити один продакшн-додаток на Node.js, і скільки інстанцій сервера потрібно для певного обсягу трафіку? Відповідь залежить від набагато більшої кількості факторів, ніж лише від фреймворку.

Ціль, до якої варто прагнути

За даними, платформа для створення зображень PicsArt обробляє близько 40 000 запитів на секунду за допомогою бекенду, який переважно складається з Node.js.

Це величезна кількість, і вона залежить від багатьох факторів, окрім бекенд-сервера. Для цього аналізу всі інші фактори відкидаються, залишаючи лише апаратне та програмне забезпечення бекенду, а саме Node.js.

Що показують синтетичні тестування

Команда, яка стоїть за Fastify, опублікувала тестування, згідно з яким Express обробляє приблизно 15 000 запитів на секунду, тоді як базовий HTTP-модуль Node досягає близько 70 000.

MacBook Pro 15″ 2015 року випуску показав майже ідентичні показники. Навіть 15 тис. запитів на секунду від Express виглядає чудово, але такі тестування проводяться з використанням дуже примітивного обробника, який зовсім не є справжньою API. Те, що робить API „реальним“, — це дані, спосіб їхньої передачі та взаємодія компонентів, тому реалістичний обробник має взаємодіяти з базою даних чи іншими сервісами.

Різниця між примітивним та Express обробниками все одно не є дуже значущою. Такі усталені фреймворки, як Express, зазвичай є кращим вибором, адже на практиці жоден окремий екземпляр не зможе обробляти 100 тис. запитів на секунду. Тому для цих тестів було встановлено Express разом із простим обробником, який виконує різноманітні операції.

Обробник тестів

Обробник виконує шість кроків:

  1. Додає нового користувача до бази даних.
  2. Завантажує 20 користувачів з бази даних.
  3. Форматує та аналізує масив з 20 користувачів.
  4. Рекурсивно перетворіть ці 20 об’єктів користувачів у формат camelCase.
  5. Відсортуйте масив.
  6. Виконайте прості обчислення та операції з виведенням на стандартний вивід, такі як Date.now та console.log, які також споживають ресурси обчислювальної потужності.

Розмір відповіді становив близько 12 КБ. Кожен об’єкт користувача мав розмір 609 байтів:

Результат 1: Node.js на Heroku з AWS RDS

Додаток було розгорнуто на Heroku на обладнанні за ціною 25 доларів на місяць, а запити надсилалися з максимально можливою швидкістю, яка не підвищувала середню затримку. Інстанція обробила 31 000 запитів протягом 60 секунд, що становить у середньому 515 запитів на секунду.

Результат 2: Django на Heroku з AWS RDS

Щоб побачити, як би працювала технологія без єдиного потоку, у Django був створений еквівалентний обробник за ідентичних умов та розміщений на тому самому сервері. Результат виявився трохи гіршим, ніж у Node.js: було оброблено 23 000 запитів за 60 секунд, тобто 389 запитів на секунду.

Результат 3: три інстанції Node.js на AWS Elastic Beanstalk

Третя конфігурація імітувала реальну продакшн-середовище на AWS. База даних та серверний модуль знаходилися у Франкфурті (eu-central-1) — найближчому регіоні, приблизно за 3 000 км від тестувальника. Три інстанції Node.js працювали під керуванням механізму балансування навантаження AWS. Вони обробили 19 000 запитів за 10 секунд, що еквівалентно 1 858 запитам на секунду. Під час постійного тесту навантаження було оброблено 1,5 мільйона успішних запитів протягом 13 хвилин.

Масштабування до 40 000 запитів на секунду

Одна одиниця обробки вміщує близько 600 запитів на секунду, тож скільки знадобиться для 40 000 запитів? Припустимо, що середній кінцевий пункт на великій платформі є вдвічі обтяжливішим за пристрій обробки тестів. Це дає 40 000 / (600 / 2) = 133 одиниці обробки, розташовані по всьому світу, для глобального додатку масштабу «єдинорога». Це не така вже й велика проблема.

Обтяжливіші кінцеві пункти можуть вимагати в 3 рази більше ресурсів, але кешування та комунікація на основі подій дозволяють відновити пропускну здатність, тому 2 рази є розумною середньою оцінкою.

Що уповільнює API

Незліченні фактори можуть знизити швидкість API. Це фактори, пов’язані з продуктивністю, які зустрічаються у більшості веб-додатків з боку сервера.

Розмір даних

Обсяг даних, які зберігаються в базі даних або завантажуються з неї, має велике значення, особливо у відносних базах даних, де великі об’єми інформації часто призводять до складних запитів чи інтенсивної роботи між СУБД та її дисковою пам’яттю. У будь-якому випадку більша кількість байтів означає більший час передачі даних. Зберігання файлів у базі даних є антипатерном, тож кращим варіантом є використання об’єктного сховища. Ідеально, якщо рекорд займає кілька десятків кілобайт, зазвичай від 1 до 20 КБ.

Відстань до бази даних

Більша відстань між бекендом та базою даних означає довші часи обробки запитів та отримання результатів. Уявімо два бекенди — один у Вірджинії, інший у Франкфурті — з єдиною базою даних у Вірджинії. Запит американського користувача доходить до бекенду у США протягом приблизно 10–40 мс, а передача даних від бекенду до бази даних займає 1–5 мс. Це нормально. Німецький користувач доходить до бекенду у Франкфурті за 10–20 мс, але обробка запиту до бази даних у Вірджинії займає 120–130 мс. Тому додатки, що працюють у одному регіоні, повинні розміщувати дані та логіку бекенду поруч, а глобальні додатки мають бути готові до складних інженерних рішень, щоб залишатися швидкими.

Апаратне забезпечення бази даних

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

Конфігурація бази даних

Бази даних постачаються зі стандартними налаштуваннями, тож їх не потрібно додатково керувати, але у масштабних системах вони вимагають уваги. Одним із параметрів, на які варто звернути увагу, є максимальна кількість з’єднань. Вона може стати причиною уповільнення роботи додатку чи перевантаження сервера бази даних, крім того, бібліотеки клієнтів, такі як ORM, часто змінюють цей параметр. Ще одним важливим параметром є тайм-аут з’єднання.

Кількість викликів бази даних

Справжня API може виконувати десятки окремих викликів бази даних, щоб завершити завдання, а також викликати інші сервіси, кожен з яких також робить свої десятки викликів. Одним із яскравих прикладів є API банківського моноліту у хмарному середовищі розробки, час відповіді на запит у якого становив 9 секунд. Його обробник виконував 300 рядків коду, під час чого відбувалося близько 30 викликів бази даних та приблизно 10 внутрішніх викликів сервісів. Це зазвичай трапляється при підході „спочатку код“, а не „спочатку дизайн“.

Рішення залежать від бізнес-логіки. Очереді та інші асинхронні техніки дозволяють відкласти частину роботи з базою даних у фон, що вимагає розділення великих сервісів на менші. Об’єднання запитів також може зменшити кількість викликів.

Хуки

Популярні ODM та ORM пропонують хуки або середовище проміжного програмування, які зручні для обробки певної логіки. Під час масових операцій вони можуть стати прихованою перешкодою, оскільки плагіни, такі як конвертери у формат camelCase чи pascal_case, також використовують обчислювальні ресурси та час. Хуки допомагають, але те, що в них написано та де, потребує подальшого розгляду.

Робота, що вимагає багато CPU

Завдання, які вимагають інтенсивної роботи CPU, уповільнюють сервер та змушують кінцевого користувача чекати. У Node.js багато з них спираються на пул потоків. Це не блокує асинхронний ввод-вивід, але все одно уповільнює API. Наприклад, при натисканні на кнопку реєстрації система чекає, поки пул потоків обробить пароль хешуванням, що займає понад 100 мілісекунд.

Вузькі місця циклу подій

Node.js виконує код у циклі подій на одному потоці, тому продуктивність коду впливає на всю програму. Функції JSON.parse та JSON.stringify можуть уповільнювати API, а вкладені цикли та рекурсивні функції є небезпечними. Ігнорування принципів роботи Node.js може зробити програму недоступною, залежно від обсягу даних та обсягу трафіку.

Погане проектування інфраструктури

Велика інфраструктура має не лише більше проблем, ніж моноліт, але й інші проблеми, і для їх вирішення часто потрібні складні архітектурні рішення. Через велику кількість компонентів погана архітектура суттєво впливає на швидкість роботи API та робить систему неефективною з точки зору використання ресурсів, тож доводиться платити більше за те, що могло б коштувати менше. Під час проектування складних додатків потрібно замислитися над тим, які проблеми вирішує та технологія, та знати альтернативи. Варто також врахувати, як можуть змінитися вимоги через два чи шість місяців, та перевіряти кожне рішення з урахуванням цих припущень. Також важливо зважати на умови SLA та нефункціональні вимоги, включаючи ситуацію, якщо обсяг трафіку зросте.

Масштабування та налаштування компонентів

Компоненти, додані до інфраструктури, можуть уповільнити роботу всієї платформи. Візьмемо RabbitMQ – у нього, як і в будь-якої технології, є багато налаштувань. Для початку підійдуть стандартні налаштування, але завдяки дослідженням та уважнішому розгляду конкретного компонента, перевірці налаштувань чи їх оптимізації можна значно покращити інфраструктуру. Компоненти також мають здатність до масштабування в залежності від навантаження, щоб не стати вузьким місцем.

Пов’язані матеріали