Архитектура современных веб-приложений на примере adopt.com.ua. Масштабирование

Предущие части: введение, внешние сервисы

Сейчас вся моя инфраструктура крутится на самых дешёвых и простых инстантсах. 512 мегабайт памяти, редис на 25 мегабайт, какой-то дохлый постгрес. Всего этого хватает чтобы сайт бодро работал.

Что будет, если завтра ко мне придет не 1000 пользователей в день, а 1000000? Чтобы это узнать, нужно проводить нагрузочное тестирование. Для этого есть специальные инструменты: ApacheBench, Apache JMeter, Gatling и другие. Они позволяют заскриптовать нагрузку, например пользователь заходит кликает по фильтрам, потом идет на котика, потом на блог и так далее и запустить это в тысячу параллельных потоков. После выполнения теста будет сгенерирован отчет о среднем времени доступа, квантилях, количестве ошибок и так далее.

Скорее всего, мой маленький сервер упадёт уже от ста конкуррентных пользователей. Или даже от десяти. Каждый запрос идёт в базу, достаёт оттуда данные и генерирует HTML. Учитывая, что данные у нас меняются нечасто, мы можем закешировать как результат запроса в базу, так и уже отрендеренный HTML. Rails предоставляет весь необходимый инструментарий, но в других фреймворках это тоже есть. Таким образом можно положить в Redis или Memcached сгенерированный HTML для всех страниц поиска и отдельных страниц котика и убрать обращение к базе данных и генерацию HTML.

Я предполагаю что это даст существенный прирост в производительности, но всё равно останется проблема слабого сервера—ему просто не хватит ни CPU ни памяти чтобы обрабатывать большое количество подключений. Тут нужно смотреть по стоимости и производительности—может быть будет дешевле добавить еще один такой же сервер, а может быть взять потолще. Так как наши вебсервера не хранят состояния, то можно просто их размножить. Теперь все будет упираться в базу данных. Можно добавить в кластер read-реплик, чтобы распределить нагрузку на чтение или взять сервер потолще, чтобы он смог обрабатывать большее количество запросов. Если и тут мы упиремся в лимиты, то надо смотреть по ситуации. Например, если основная нагрузка идет через поиск, то можно поднять ElasticSearch. Вариантов масса. Пока мы упрёмся в ограничения скорости работы уже самого интерпретатора Ruby, пройдет куча времени.

Естественно, до того как бросаться покупать сервера, нужно оптимизировать приложение до предела—убрать N+1 запросы, привести в порядок индексы в базе, закешировать всё что можно закешировать, упростить HTML-шаблоны. К сожалению обычно разработчики мало внимания уделяют простым но эффективным оптимизациям.

Еще одна важная особенность нашего решения—это Heroku. Он работает поверх AWS который работает поверх реального железа. То есть, просто вычислительные мощности будут снижаться с каждым добавленным уровнем виртуализации. Поэтому если на Heroku уже тесно—надо переезжать или в другое облако или на железные сервера. Но это самая сложная и дорогая оптимизация, потому что теперь нужно будет самому заботиться о серверах, делать там контейнерную оркестрацию, проектировать CI/CD и так далее.

Большинство компаний любят заниматься преждевременной оптимизацией и оверинжинирингом без реальной потребности. Я тоже в своё время был адвокатом микросервисов и реактов, но сейчас—приверженец прагматичного подхода—вначале берем самые скучные и простые технологии, выжимаем из них максимум и только когда этого начинает не хватать—думаем в сторону альтернативных решений.


Понравился материал? Подписывайся на мой телеграм канал: https://t.me/full_of_hatred