Сформировать 400 000 документов за рабочий день, одновременно загрузить информацию о тысячах доменов, в 10 раз увеличить скорость обработки данных сайта, обеспечить стабильность интернет-магазина при росте посещаемости — решать такие задачи помогает горизонтальное и вертикальное масштабирование. Разбираем на примере наших проектов и кейса клиента, как повысить производительность веб-проекта.
Кейс 1: ускорили время формирования документов с 24 до 8 часов
Для подготовки клиентских документов мы используем внутренний самописный сервис. У нас есть биллинговая система, которая фиксирует все транзакции. Сервис берет необходимые данные и на их основе генерирует готовый файл. Так раз в месяц мы формируем отчетность для пользователей, это более 400 000 документов на основании более 5 млн транзакций.
В пиковый момент система начала работать медленно: документы формировались больше 24 часов. Из-за низкой скорости процессов внутри сервиса пользователям приходилось долго ждать отчетные документы по услугам.
Решение. Пошли путем вертикального масштабирования и повысили вычислительные возможности сервера. Увеличили ядра процессора с 4 до 32 и добавили оперативки с 8 до 24 ГБ. Архитектуру приложения не меняли.
Чтобы проверить работоспособность системы с новым процессором и памятью, сделали сетап отдельного сервера и подняли нужный контейнер. В нем создали окружение, как в настоящей системе, и запустили тестовое формирование документов.
Во время тестирования были сложности: нарушение прав доступа, несовместимость библиотек, необходимость репликации базы данных и тому подобное. Но это стандартные вещи, тем более что наш сервис для подготовки документов является частью внутреннего многолетнего проекта компании и содержит легаси-код. Чтобы увеличить производительность системы, мы перешли с PHP 5.6 на PHP 7.4, который работает в 1,5–2 раза быстрее. Из-за этого некоторые библиотеки оказались несовместимы.
После того как устранили ошибки, система стала формировать на 15% больше документов и делать это в три раза быстрее — за 8 часов. Предел производительности теперь составляет 1,5 млн документов.
Кейс 2: повысили скорость загрузки данных при обращении к сторонним сервисам
Мы разработали приложение, которое показывает информацию о доменах: принимает запрос пользователя, отправляет его к Whois-сервису, а затем выдает ответ. Раньше две эти операции происходили последовательно, поэтому ответ мог занимать от 1 до 5 секунд. Например, на загрузку информации о 20 доменах требовалось от 20 до 100 секунд.
Но иногда возникали дополнительные задержки более 30 секунд. Так как Whois-сервисы имеют лимиты на количество запросов в единицу времени с одного IP-адреса, например 20 операций в секунду, наш самописный сервис иногда не справлялся с нагрузками. При этом за нарушение лимитов IP-адрес могли просто заблокировать.
Решение. Пошли по пути горизонтального масштабирования и добавили прокси-сервер с пулом IP-адресов.
В основе прокси-сервера лежит самописный демон на Perl. Он назначает IP-адреса на запросы пользователей и направляет их к конкретному Whois-сервису в соответствии с таблицей динамической маршрутизации.
Так выглядел код, когда запрос шел напрямую к Whois:
public function getAnswer(): string
{
$output = '';
$conn = fsockopen('whois.nic.ru',43 , $errno, $errstr, 30);
stream_set_timeout($conn, 43);
fputs($conn, $domain. "\r\n");
$time = time();
while (!feof($conn)) {
$output .= fgets($conn, 128);
if (time() >= $time + 30) {
fclose($conn);
throw new SocketException('Whois Error: Connect timeout');
}
}
fclose($conn);
return $output;
}
Вместо whois.nic.ru стали обращаться к прокси-серверу. Выбрав такое решение, мы внесли минимальные изменения в проект.
В результате масштабирования через пул IP-адресов удалось распараллелить обращения к Whois-сервисам и повысить производительность системы по bandwidth.
Но у такого решения есть ограничения. Если потребуется собирать данные, например, о 100 000 доменов, IP-адресов не хватит. И даже мощный сервер с большим объемом памяти не решит проблему. В этом случае нужно пойти другим путем — например, снизить скорость запросов, увеличив холд между ними.
Кейс 3: увеличили производительность внутреннего сервиса в 10 раз
Среди самописных приложений у нас также есть сервис мониторинга, который обрабатывает результатов проверок доступности сайтов. Чтобы обеспечить бесперебойность в бизнес-процессах пользователей, необходимо было сделать сервис отказоустойчивым и высокопроизводительным.
Решение. Здесь так же, как и в прошлом кейсе, применили горизонтальное масштабирование:
Повысили отказоустойчивость. Сервис мониторинга использует множество различных нод, расположенных в том числе не на нашем хостинге, — так он проверяет доступность сервисов из разных концов страны. Чтобы обеспечить доступность данных на каждой ноде, мы настроили репликацию базы данных MongoDB. Выбрали эту БД, поскольку у нее высокая надежность, она совместима с проектами на Python и имеет штатный механизм репликации.
Также мы дублировали все микросервисы, которые есть в сервисе мониторинга: микросервис репликации MongoDB, проверки, отправки результатов проверки в брокер сообщений, микросервис, который читает сообщения, и другие.Повысили скорость обработки данных. Для этого использовали шардинг: разделили один сервис, который шлет сообщения в брокер сообщений, на десять сервисов. Каждый получившийся сервис начал работать только с одной очередью сообщений. Бенчмарк-тесты показали, что скорость взаимодействия выросла: раньше было 1500 сообщений в секунду, стало — 15 000.
Кейс 4: распределили нагрузку на сервер с помощью балансировщика
Это кейс нашего клиента, который держит на хостинге интернет-магазин. Он масштабировал проект, когда выросло количество посетителей: потребовался отказоустойчивый сервер, чтобы сайт внезапно не упал.
Решение. Клиент применил горизонтальное масштабирование с помощью нашего балансировщика нагрузки Load Balancer. Балансировщик представляет собой два кластера серверов в разных дата-центрах с резервированием и автоматическим failover. Работает по двум алгоритмам балансировки: Round Robin / Weighted Round Robin и Least Connections. Умеет проверять доступность, запоминать сессии, поддерживает proxy protocol и backend keep-alive.
Клиент скопировал свой виртуальный сервер, настроил репликации баз данных между исходниками и копией и синхронизировал их. Так получилось два идентичных сервера.
Затем в локации, где расположен первый сервер, подключил балансировщик с алгоритмом работы least connections и правилом балансировки двух серверов. После того как балансировщик был создан, настроил в DNS его домен. Так получилась отказоустойчивая система интернет-магазина.
Горизонтальное масштабирование в рамках облака с помощью балансировщика, как в этом проекте, — надежное решение. При этом каждый элемент можно еще масштабировать вертикально, например увеличить мощности облачной базы данных или мощности каждой ноды.
Краткие итоги
Мы стали задумываться о масштабировании уже на этапе разработки. Если это возможно, сразу закладываем запас по производительности и обеспечиваем отказоустойчивость. Так потом не приходится вносить глобальные изменения в архитектуру. Если масштабировать нужно готовый проект, стараемся делать это с минимальными вмешательствами — например, как было с сервисом, который формирует отчетные документы.