Когда я начинал карьеру лет 15 назад, иногда делал сайты на PHP, Perl, RoR, ASP… Но из-за слишком частой смены стандартов в Web, я выгорел и "ушел" целиком в Backend + Desktop-разработку, где всё плюс-минус стабильно годами, если не десятилетиями.

Однако, в условиях растущего запроса на кроссплатформенность, все чаще задумываюсь о возвращении к Web, и так как я больше всего использую .NET, решил разобраться детальнее с Blazor, а точнее, с Interactive Server-Side Rendering.

Довольно быстро освоил как оно работает, но остался незакрытый вопрос — а что там с производительностью? Что если много людей одновременно будут жать кнопочки в системе? Какая вообще нагрузка на сервера?

Не найдя подробные разборы в открытых источниках, я за недельку по вечерам сделал игрушку для Telegram App, и в пятницу 26 июля 2024 собрал людей со своего и дружественных сообществ на исследование нагрузки и возможных инцидентов. Собранную информацию с деталями раскрываю в этой статье.

Контекст: Правила игры и архитектура решения

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

Игра — «Угадайте число», многие ее знают под названием «Быки и Коровы». Суть исходной игры заключается в том, что один игрок загадывает число из уникальных цифр, а второй должен его угадать. В помощь угадывающему, на каждую его попытку, загадывающий должен называть сколько есть «Коров» (количество угаданных цифр не на своих местах) и «Быков» (количество угаданных цифр на своих местах) в названном числе.

Моя реализация этой игры отличается от оригинала тем, что число загадывает сервер, а угадывает его не один игрок, а все игроки вместе. Для простоты регистрации игра была адаптирована под запуск через Telegram Mini Apps, благодаря чему с минимальными затратами мне удалось получить готовые ID пользователей не затрачивая время и силы на создание своей системы регистрации.

Интерфейс игры представляет из себя клавиатуру из 9 цифр, 4 экранов для отображения введенного числа, 4 экранов для «лампочек» подсказки отгадывания, а также несколько экранов для статистической информации. При каждом нажатии кнопки «клавиатуры» происходит впечатывании цифры, а после ввода четырех цифр — происходит сверка угадываний и вывод результата сверки («коровы» — желтые лампочки, «быки» — зеленые).

Интерфейс игры, снятый из браузера при разработке
Интерфейс игры, снятый из браузера при разработке

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

Все описанные выше действия были реализованы строго на C# и согласно технологии Server-Side Rendering должны обрабатываться на стороне сервера. А так как сравнение введенного числа с загаданным, обработка статистики, устранение конфликтов многопользовательской игры могут давать свою нагрузку на сервер — проект был разделен на две части, которые работали на разных серверах:

Часть / Сервер 1 (Front) — ASP.NET приложение с включенным Blazor Interactive Server-Side Render, которое обрабатывает только изменения интерфейса и отправляет запросы по API на второй сервер.

Часть / Сервер 2 (Back) — ASP.NET приложение с включенным Quartz для регулярного обновления загаданного числа, WebApi для обработки запросов от Blazor, а так же с PostgreSQL для сохранения статистической информации. При этом все данные об игре держатся в оперативной памяти, а для ускорения работы SELECT в БД не используется.

Схематичное изображение архитектуры
Схематичное изображение архитектуры

Первый взгляд на результаты

В игре за день приняло участие 37 разных игроков. Которые сыграли 445 раундов и сделали 7250 попыток отгадать число.

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

Сервер 1 - (Front / Blazor Server-Side Rendering)

Сервер 2 - (Back / Quartz + WebApi + PostgreSQL)

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

Сервер 1 (Front / Blazor Server-Side Rendering)

Сервер 2 - (Back / Quartz + WebApi + PostgreSQL)

Разбор инцидента, произошедшего в обед

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

С учетом того, что я не тратил много времени на разработку, то допускал такие падения с быстрым решением путем «Перезапустить приложение». Но каково было мое удивление, когда я вообще не смог попасть на сервер ни по RDP, ни по SSH. По итогу его пришлось перезагружать полностью, после чего бэк-приложение успешно запустилось и игра ожила.

На странице мониторинга эта ситуация на бэкенде выглядела следующим образом:

Рост нагрузки виден в двух местах:

1 — Возросшая сетевая активность до 100kBps, что на самом деле не является большим показателем, но выбивается из общей картины.

2 — Возросшая нагрузка на диски до 30 MBps, которая вызвала вопросы. Здесь можно предположить, что произошел спам со стороны фронта, но с учетом того, что попытка отгадывания отправляется с фронта только после ввода четырех цифр, а фиксируется она всего одним INSERT-ом, следовало бы ожидать более высокой сетевой нагрузки на сервер.

Но даже если гипотеза со спамом попыток верна, то значит в таблице с попытками пользователей должно быть много записей на момент начала зависания. Но в базе данных предпоследняя попытка перед зависанием была в 12:51:11, а последняя в 12:53:19 — не такая уж и серьёзная нагрузка, она была примерно такой почти весь день:

Результат запроса попыток всех пользователей с 12:50
Результат запроса попыток всех пользователей с 12:50

Следующим подозреваемым стал Quartz — а что если он сошел с ума и начал срабатывать не раз в 2 минуты, а чаще? Ведь при срабатывании Quartz происходит генерация нового числа, а с ним выполняется не только запись этого числа в базу, но и сбор+запись статистики за раунд + за час. Но и эта гипотеза оказалась ложной после анализа базы данных — предпоследний код был сгенерирован в 12:50:57, последний перед зависанием в 12:52:57 — строго через 2 минуты, а следующий раз был уже в 13:20:38, после перезагрузки:

Результат запроса сгенерированных кодов с 12:50
Результат запроса сгенерированных кодов с 12:50

Таким образом получается, что непосредственно ни игровой движок, ни сама СУБД, не являются причиной сбоя. Возможно, что-то пошло не так на уровне системы, либо инфраструктуры облачного провайдера. Ведь особенно странным является факт недоступности даже по SSH.

На этом можно было бы довольно хлопнуть в ладоши закрыв инцидент как псевдо-ложный, но вызвал вопрос график нагрузки на CPU и сеть фронта:

Здесь я увидел потенциально главный минус балл, который захотелось сразу поставить Блазору — нагрузка на CPU, но разбор ситуации заставил меня передумать.

Довольно быстро я сообразил, что в высоком потреблении процессора и сети виноват таймер обратного отсчета, который при достижении 0 секунд выполняет запрос актуальной статистики с бэка и только при ее получении возвращает отсчет на 120 секунд. Так как таймер срабатывает каждую секунду, то получается, что каждую секунду создается новый поток обработчика таймера, в котором отправляется запрос на бэк, который в свою очередь впадает в ожидание до таймаута и до этого момента поток потребляет процессорное время. А так как таймаут примерно минута, то за минуту копится порядка 60 потоков на игрока (открытую страницу с Blazor-приложением в браузере).

И здесь можно было бы опять хлопнуть в ладоши, сказав «Так вот в чем причина!», но возникает немой вопрос — «А почему таймер не перестает работать при условии, что игроки увидев нерабочую игру повыходили из игры? Почему сессии на Interactive SSR не погасились?» Ответ я найти не смог, но возможно знатоки Блазора знают как обрабатывать такие случаи, приглашаю их написать ответы в комментариях.

Разбор стресс-тестирования

По итогу дня я пришел к выводу, что графики выглядят через-чур красиво. И проведенный анализ базы данных показал, что за весь день не было такого случая, чтобы в одном раунде играло больше 4 человек. А это совсем никакой показатель для теста. Поэтому я кинул клич в нескольких сообществах и собрал 9 добровольцев, которые с 19:00 до 19:05 в игре просто тыкали кнопки в быстром ритме не пытаясь угадать число.

По итогу получилось где-то около 4500 запросов за 5 минут, или примерно 15 запросов в секунду. Конечно, это не уровень условного Яндекса, но и серверные мощности у меня не очень большие. И вот какой результат вышел на мониторинге:

Сервер 1 - (Front / Blazor Server-Side Rendering)

Сервер 2 - (Back / Quartz + WebApi + PostgreSQL)

Всплески нагрузки видны, но с учетом пиковой нагрузки на Front CPU 3.5% и 60kBps сети, кажется, можно признать Blazor жизнеспособным решением для продуктивной нагрузки. Но, конечно, всегда есть но.

Некоторые из игроков во время стресс-тестирования жаловались на одну и ту же проблему: Иногда не прожимаются кнопки. Это было видно, когда набивали их последовательно, например нажимали 1 2 3 4, а вводилось только 1 2 4 (3 как будто бы не нажималась).

Как показало общение – у всех это происходило не одновременно,. Сам я повторил проблему и позже стресс-тестирования при меньшей нагрузке на сервер. Из этого я делаю вывод – Blazor Interactive SSR (а скорее соединение SignalR) не может держать столь большое количество обращений между сервером и одним и тем же клиентом, в результате чего пропускает часть.

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

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

Неприятный серый экран при потере связи с сервером
Неприятный серый экран при потере связи с сервером

Как выяснилось, это нормальное поведение при потере соединения SignalR с сервером Blazor. С одной стороны, это правильно — если нет соединения, значит команды не отработают, и пользователь должен знать об этом. С другой стороны, а что если пользователь хочет методично почитать информацию со страницы? Ему будет мешать эта подложка загрузки? Возможно, профессионалы Blazor знают как решать подобные проблемы, так же приглашаю написать в комментарии.

Подведем итоги исследования

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

Плюсы:

  • Легкий вход в технологию разработчика, который знает C# /.NET. Это означает, что к разработке фронт‑энда можно подключать разработчиков бэк‑энда, которые могут не разбираться в тонкостях языков JS/TS и архитектуре фреймворков типа AngularJS, Vue.js. А на тех проектах, где не требуется реализация сверх-красивого интерфейса, скорее всего можно вообще обойтись без фронт-специалистов (для небольших интеграторов это может означать хорошую потенциальную финансовую выгоду);

  • Решение проблемы передачи сложных объектов между браузером и сервером. В прошлом я немало провозился с передачей объектов с десятком полей между бэком на C#/Java и браузерным JS/TS, и прекрасно понимаю эту боль. То, что Blazor позволяет получить по API сразу нативный C#-класс и сразу же его отобразить в данных на веб-странице, кажется весьма весомой фичей.;

Минусы:

  • Нет возможности сделать микс из Interactive Server-Side Rendering и Client-Side Rendering (WebAssembly). Как показала практика игры — целый ряд действий (например, отсчет таймера, просто вычитание единицы из int переменной timeToNextRound) через рендер на сервере является крайне нездоровой историей. Но, с другой стороны, включение всего рендера на стороне клиента обременяет отправкой нескольких десятков мегабайт Runtime в браузер клиента, что тоже плохая идея (и не факт что будет нормально работать на слабых устройствах). Поэтому кажется, что не хватает какой-то промежуточной истории. Будет здорово, если Microsoft сделает некий транслятор C# в JS для методов с простыми вычислениями и изменениями примитивов. Но, пока этого нет – истории с постоянным изменением данных, которые по факту не зависят от бэк-сервера, выглядят убийственными не только для производительности фронт-сервера, но и для сети;

  • Непрозрачная отладка SignalR. Периодически во время разработки у меня происходил какой-то сбой и нажатые кнопки в браузере вроде отрабатывали, но вот точка останова в Visual Studio не срабатывала. Почему – непонятно. В браузерных средствах разработчика на панели «Сеть» никаких данных про отправку запросов и получение ответов тоже не было. В целом спасала повторная сборка / перезапуск проекта, но какое-то время у меня это съело, что однозначно записываю как негатив.

  • Потеря вызовов при срабатывании обработчиков чаще 2-3 раз в секунду. Хоть эта история возникает только в тех случаях, когда пользователь истерично жмет по кнопкам, которые имеют под собой обработчики, и в реальности, за исключением игр, такое вряд ли будет происходить, этот момент однозначно стоит держать в голове.

Лично для себя я сделал вывод, что несмотря на все озвученные минусы, у технологии есть свое место в корпоративном мире / в закрытых контурах и я однозначно буду агитировать использовать Blazor большинству команд, которые пишут на C#. А какие выводы делаете вы? Поделитесь в комментариях!

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


  1. Gl-lab
    30.07.2024 06:08

    Нет возможности сделать микс из Interactive Server-Side Rendering и Client-Side Rendering

    Но ведь в .net 8 как раз добавили режим Auto, или это не то что вам нужно?

    • Interactive Auto (automatic) rendering to initially use the server-side ASP.NET Core runtime for content rendering and interactivity. The .NET WebAssembly runtime on the client is used for subsequent rendering and interactivity after the Blazor bundle is downloaded and the WebAssembly runtime activates. Interactive Auto rendering usually provides the fastest app startup experience.


    1. Drazd Автор
      30.07.2024 06:08

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

       after the Blazor bundle is downloaded

      То есть скачивание рантайма все-равно происходит, а в условиях плохой сети и дорогого трафика это не очень интересно. Хотелось бы именно работы в браузере какого-то скомпилированного JS из C# без рантаймов.

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


  1. ColdPhoenix
    30.07.2024 06:08
    +1

    В браузерных средствах разработчика на панели «Сеть» никаких данных про отправку запросов и получение ответов тоже не было.

    Насколько помню там WS соединение должно быть(по хорошему), оно отображается, но, возможно его надо ловить в момент открытия, а потом уже можно смотреть сообщения.

    Нет возможности сделать микс из Interactive Server-Side Rendering и Client-Side Rendering (WebAssembly).

    Сейчас вроде есть режим Auto, когда он из ISSR перейдет в ICSR в фоне. Но там есть свои нюансы.

    Там на самом деле очень много "ручек", но например обратный отсчет это пример когда ISSR подходит плохо, тут уж лучше JS чучуть оставить.(не зря в MudBlazor + MudExtensions есть компонент Watch)

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

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

    В целом статистика неплохая на самом деле, проблема с таймером это иное все же.(потому под такие вещи я предпочитаю просто сервис фоновый)

    C пропусками событий, странно, надо попробовать повторить тест как нибудь.

    Для себя пока пилю ICSR приложение(так как там тяжелое приложение). На работе тестируем возможность(скорее думаем, стоит ли переписывать) сделать панель управления, но Blazor строго после авторизации.


  1. ProgerMan
    30.07.2024 06:08

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

    Поэтому решил, что Blazor буду использовать чисто для фронта, оставив всю бизнес-логику API. Так всё работает прекрасно.