Привет, Хабр. Я — Влад Климанов, бэкенд-разработчик в ДАЛЕЕ. В декабре 2022 года мы запустили сайт Финзачета — самого массового зачета по финансовой грамотности в стране. Организатор — Банк России. Событие ежегодное, работаем с сайтом уже два года. Сегодня расскажу о том, как обеспечивали отказоустойчивость сайта.

О проекте

Финзачет — это социальный проект, направленный на повышение финансовой грамотности. Это актуальная задача для российского общества. За последние 4 года этот показатель у населения возрос. У Банка России и Минфина даже есть целая стратегия по повышению финансовой грамотности на 2017-2023 годы. Проект мне сразу приглянулся — делать что-то полезное для обычных людей всегда интересно. Ну и тематика из разряда вечнозеленых — деньги и как их сберечь и приумножить. Особенно в свете того, что в прошлом году мошенникам удалось украсть почти 15 млрд рублей у россиян.

Я курировал проект как лид-разработчик, делал весь бэкенд на Laravel, настраивал инфраструктуру и работал с формированием готовой аналитики. Сегодня хочу рассказать о том, как мы в срок за 1,5 месяца разработали сайт и подготовили его к нагрузкам 150 000 уникальных пользователей в час. Про дизайн и менеджмент на проекте можно прочитать в другой статье.

Какие задачи стояли перед нами

Приведу несколько ключевых требований клиента к бэкенду сайта:

  • 150 000 тысяч пользователей должны иметь возможность одновременно проходить зачет.

  • У пользователя есть неограниченное количество попыток прохождения зачета. В зачете 20 вопросов, в версии для школьников — от 4 до 15, в зависимости от возраста.  

  • Бэкенд формирует разные типы вопросов под разные аудитории. 

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

Что делали для снижения нагрузки

В этом проекте мы использовали методологию 12-факторного приложения. Помимо этого мы использовали CDN для кэширования статического контента часто используемых элементов сайта. 

CDN был нужен, так как зачет всероссийский — участники регистрировались и проходили тестирование во всех регионах страны. Общее количество посещений сайта составило порядка 3 миллионов.   

А вот разбивка по наиболее активным регионам. И это не считая того, что остальные регионы тоже участвовали. 

Пара слов о методологии 12-факторного приложения и CDN для тех, кто не в курсе
  1. Методология 12-факторного приложения. Это значит, что база данных (БД), кэш и пользовательские сессии представляют собой подключаемые к бэкенду ресурсы. Балансировщики нагрузки распределяют пользовательский трафик на пул бэкендов. Последний можно расширять добавлением новых серверов для обработки большего числа запросов. А еще можно масштабировать все слои архитектуры независимо друг от друга, добавляя ресурсы по мере надобности. Сейчас эта методология — индустриальный стандарт. 

  1. CDN и кэширование часто используемых элементов сайта. Это помогает:

  • Снизить нагрузку на бекенд, увеличивая эффективность обработки бизнес-логики;

  • Повысить отказоустойчивость сайта, ведь помимо больших суточных нагрузок проект никак не ограничивает пользователей по количеству прохождений зачета.

Если говорить в контексте балансировки нагрузки, то у нас имеется веб-сервер, принимающий пользовательские запросы. На него приходил основной трафик, который обрабатывался либо с помощью кэша, либо за счет проксирования этого запроса на бэкенд. 

Проект — это, по сути, SPA без SSR, что позволяло эффективно отдавать статичные данные главной страницы, личного кабинета и так далее. Здесь может возникнуть вопрос о поисковой оптимизации, отвечаю сразу: страниц, подпадающих под SEO, очень мало, на практике они постоянно закэшированы. Также мы перенесли в CDN ссылки на подключаемые ресурсы (картинки, шрифты и т.п.). Таким образом снижалось количество трафика на бэкенд.

По сути, происходило следующее: 

  • Пользователь открывал страницу, в html-коде которой прописаны ссылки на подключаемые ресурсы, 

  • Они ведут не на этот же самый сайт, а на CDN и в итоге отдаются распределенно за счет стороннего облака. 

От одного пользователя до фронтенда приходил один запрос. Допустим, юзер открывает главную — в этот момент подгружаются JS и html со сторонних ресурсов. А дальше, когда он переходит по сайту, передаются уже более легкие запросы к бэкенду, которые были дополнительно оптимизированы. В целом, мы старались с помощью CDN снизить количество прямых запросов к сайту.

Отдельно оптимизировали запросы для работы с пользовательскими базами и сессиями:

  • добавляли в необходимых местах индексы, 

  • меняли синтаксис запросов, чтобы планировщик лучше отрабатывал их с учетом имеющихся данных. Например, через EXPLAIN ANALYZE валидировали запросы, генерируемые ORM. А еще упрощали или переформулировали их, в некоторых случаях переписывали на Raw SQL. 

Мы запрофайлили весь сайт, прошлись по пользовательскому пути, уделяя внимание частям, к которым может быть много запросов. Там мы либо снижали количество запросов, либо оптимизировали скорость ответа, чтобы очередь на обработку не переполнялась. Мы аккумулировали промежуточные результаты на стороне клиента, а потом отправляли на сервер одним запросом. Смысл здесь в том, чтобы снизить количество запросов к бэкенду там, где это позволяют сделать бизнес-логика и требования. Ну и в том, чтобы увеличить вероятность попадания в кэш. Бонусом добавили дополнительные сервера. Лучше перезаложить мощностей, чем недозаложить — это правило надо отлить в золоте. 

В качестве балансировщика использовали Nginx. Мы могли без особых проблем распределять пользовательский трафик стандартным методом через weighted round-robin, объединив все бэкенды в один upstream. Он дает возможность указать несколько серверов, на которые Nginx уже будет сам проксировать запросы. 

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

О логике формирования тестов

Зачет при прохождении состоит из 20 вопросов. В базовом уровне при повторной попытке они не менялись. А вот в продвинутой версии для взрослых групп каждый раз собирался новый набор вопросов из общей базы в 34 задания. То есть если человек захочет пройти зачет pro-уровня еще раз, то задания будут другими. Он может пересекаться с первой попыткой, но не на 100%. 

Логика формирования набора вопросов основана на привязке к профилю юзера и выбранному уровню сложности: возраста, уровня образования, сложности и др. Для каждого пользователя в зависимости от его профиля подбирался индивидуальный набор вопросов. А для семейного зачета они подбирались таким образом, чтобы в выдаче присутствовали вопросы для каждой возрастной группы, чтобы всем достался вопрос: от самого маленького до самого взрослого.

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

  • Региона РФ;

  • Возрастной группы;

  • Уровня образования

  • Уровня сложности.
    То есть бэкенд должен был быть способен аккумулировать эту информацию.

Вот 5 самых грамотных регионов по результатам Финзачета. Передаем привет читателям из этих мест, вы — мощь.
Вот 5 самых грамотных регионов по результатам Финзачета. Передаем привет читателям из этих мест, вы — мощь.

Напрашивается решение: сохранять результат ответа на каждый вопрос (1 вопрос — 1 запрос). И поначалу мы думали реализовать этот вариант. Но это бы слишком увеличило нагрузку на бэкенд. 

И еще момент — по опыту прошлого года мы знали, что могут быть ситуации, когда пользователь бросал прохождение зачета на полпути. Причины могут быть разные, но результат один — для статистики пользователь начал тест и ответил на 0 вопросов. А по факту он мог ответить на 5 или 10. Такие детали клиент тоже хотел отследить.  

Возникла дилемма: с одной стороны нужна максимально детализированная статистика, с другой — есть риск перегрузить бэкенд. В итоге пришли к компромиссу: вопросы решили сохранять локально, а если человек уходит со страницы, всплывает окно с кнопкой и вопросом «Вы точно хотите выйти?». В этот момент посылается запрос с ответами на сервер с меткой, что пользователь завершил прохождение зачета, и с количеством ответов. То есть сохраняется промежуточное состояние. И вместо запросов по каждому из вопросов мы собираем все в один и отправляем. Получается снижение нагрузки в 20 раз.  

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

Запросы отрабатывали со скоростью меньше 100 мс. Поэтому при переходе между страницами пользователь не замечал, что что-то происходит. 

Скажу еще пару слов о логике результатов. В конце проводится валидация ответов на правильность. Смысл Финзачета в том, чтобы чему-то научиться. При ответе на вопрос всплывает уведомление, что он принят. В конце пользователь узнает количество правильных и неправильных ответов, а к последним еще и выводятся примечания и рекомендации со ссылками на образовательные материалы. Для Банка России важно было показать человеку, в чем он ошибся и мотивировать его изучать информацию дальше. 

P.S.: если вы проходили Финзачет (ну мало ли), поделитесь в комментариях результатом.

О сборе аналитики

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

  • какие данные понадобятся, 

  • как планируется их агрегация,

  • какой способ хранения позволит удобно сохранять их на этапе прохождения зачетов.

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

Без огрехов не обошлось: например, в первые дни после запуска, когда данные уже копились, мог вылезти какой-то баг. Его правили, но в аналитике он отображался как аномалия, всплеск непонятных чисел. Предусмотреть все баги до запуска в подобном проекте не всегда возможно. Выходили из ситуации за счет постоянного наблюдения за данными в течение проекта.

Аналитику собирали по двум большим направлениям: вопросы и попытки пользователей. У первых были четко прописанные категории, уровень сложности, возрастная группа. У вторых — другие параметры вроде возраста, уровня образования, региона проживания и т.д. На основе всех попыток человека мы определяли первую и лучшую. Это необходимо, чтобы понимать, насколько успешно пользователи справлялись при первом взаимодействии с вопросом. Потому что все мы знаем, что с неограниченным количеством попыток прохождений можно быстро натренироваться и с ходу правильно ответить на все. И лучшая попытка по факту уже не будет отражать реальной сложности вопросов.   

Технически на основе сырых данных из БД мы формировали обезличенную выгрузку по всем попыткам и загружали её в Pandas. Там уже обрабатывались данные и готовились excel-файлы с настроенными сводными таблицами, а также отчет с самыми важными метриками. На их основе менеджеры уже делали презентации и отчитывались перед коллегами из Банка России. 

И еще немного занимательной статистики об участниках зачета  
И еще немного занимательной статистики об участниках зачета  

Результаты

Финзачет проходил с 1 по 18 декабря 2022 года. Вот результаты в цифрах:

  • Среднесуточная посещаемость — 230 000 человек

  • Пиковая посещаемость — 425 000 пользователей за день

  • Общее количество посещений за все время работы проекта — ~ 3 525 000 пользователей

  • Количество уникальных пользователей, прошедших зачет до конца — ~1 500 000 человек

  • 75% участников предприняли 2 попытки прохождения зачета

  • Общее количество попыток прохождений зачета — ~2 600 000. 

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