Всем привет! На связи ITSumma.За 15 лет, что мы делаем нагрузочное, у нас накопился список самых распространенных ошибок, которые совершают, когда строят и отлаживают инфраструктуру.

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

Отсутствие какого-либо приемлемого мониторинга 

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

Например, в одном из кейсов, мы на первом этапе мы провели 3 итерации, запустив относительно легкий скрипт с использованием Яндекс.Танка в качестве инструмента тестирования и мониторинга показателей. “Уронить” сайт удалось только после запуска шести экземпляров Я.Т с разных серверов.

При этом сам Яндекс.Танк и мониторинг клиента показывали противоречивые результаты — цифры колебались в пределах 4000-6000 RPS. После настройки мониторинга удалось получить достоверные предельные показатели нагрузки в 8000 RPS. 

График показателей Nginx RPS
График показателей Nginx RPS
График показателей CPUs Load Average
График показателей CPUs Load Average

Дальнейшие исследования выявили, что проблема находится в Docker-контейнерах, т. к. запуск тестирования в обход позволил преодолеть рубеж в 8000 RPS. Определить это было бы невозможно без настройки мониторинга на сервере клиента с использованием essm, благодаря чему мы получили точные показатели нагрузки и метрик Nginx.

Некорректные настройки ядра ЦП и ОС

В том же кейсе мы столкнулись с другой распространенной ошибкой — неправильных настройках для процессора и операционки. Из-за некорректных настроек может не хватать ресурсов процессора на веб-сервере при большом объеме запросов. Что приводит к перегрузке сервера и снижению отзывчивости системы. 

Эту ошибку можно обнаружить, когда вы проверяете обращения к бэкенду. У нас нагрузка постепенно увеличивалась с 1 RPS до 3000 RPS. После двух минут тестирования при уровне нагрузки в 315 RPS ресурсы CPU на веб-сервере закончились. Процессор стал обрабатывать запросы с задержкой, а PHP-FPM стала функционировать нестабильно — в логах появились ошибки server reached pm.max_children и upstream timed out.

Спустя минуту после достижения пика нагрузки, все воркеры PHP оказались полностью заняты. В результате главная страница открывалась почти 17 секунд!

Результаты показаны на рис. 1-3

Показатели времени ответа главной страницы сайта при нагрузке 315 RPS на веб-сервере.
Показатели времени ответа главной страницы сайта при нагрузке 315 RPS на веб-сервере.
Показатели общей пропускной способности сетевого канала при нагрузке 315 RPS на веб-сервере.
Показатели общей пропускной способности сетевого канала при нагрузке 315 RPS на веб-сервере.
Топ процессов, потребляющих ресурсы WEB-сервера при нагрузке 315 RPS
Топ процессов, потребляющих ресурсы WEB-сервера при нагрузке 315 RPS

Вторую итерацию мы провели по тому же сценарию — постепенный рост нагрузки с 1 до 3000 RPS. но с обращениями только к динамическому контенту. В результате тестирования была достигнута нагрузка в 715 RPS, и дальнейшее увеличение нагрузки привело к исчерпанию ресурсов процессора, а в логах Nginx и PHP-FPM появились ошибки вида:

WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 32 children, there are 0 idle, and 804 total children

WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 32 children, there are 0 idle, and 836 total children

Топ процессов, потребляющих ресурсы WEB-сервера при нагрузке 715 RPS
Топ процессов, потребляющих ресурсы WEB-сервера при нагрузке 715 RPS
Показатели Nginx RPS на веб-сервере при нагрузке 715 RPS
Показатели Nginx RPS на веб-сервере при нагрузке 715 RPS

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

Статический контент раздается бэкендом

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

Это увеличивает время загрузки страниц и снижает общую производительность системы. Часто так бывает, когда клиент работает в CMS Bitrix, т. к. в настройках по умолчанию, она хранит файлы и обращается к статике непосредственно на диске. В таких случаях рекомендуется оптимизировать доставку статического контента, например, использовать кэширование или CDN, чтобы снизить нагрузку на сетевой канал и улучшить время загрузки страниц.

Найти эту проблему порой бывает довольно сложно. Иногда приходится провести 3 итерации тестирования по разным сценариям:

  1. Схема 1 сценария: Главная → Подбор мест на главной → Переход и подбор с изменением параметров. 

  2. Схема 2 сценария: Главная → Авторизация → История заявок.

  3. Схема 3 сценария: Главная → Подбор МКБ.

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


2023/08/09 07:44:12 [error] 10898#10898: *159968493 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 31.184.213.92, server: teststest.ru, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "teststest.ru"

2023/08/09 07:44:12 [error] 10938#10938: *159968757 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 31.184.213.92, server: teststest.ru, request: "GET /booking/?sessid=de81df3d7428a9650b77deacabd6fc48&form=1&ym=24287&adult=2&children=0&skk=375&hotel=382&m204ajax=Y HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "teststest.ru", referrer: "http://teststest.ru/booking/?skk=375&hotel=0&ym=24287&adult=2&children=0&m204ajax=Y"

При этом PHP-процессы многократно обращались к SVG-файлам, тем самым замедляя работу сайта. Происходило это из-за CMS, которая отправляла запросы к статическому контенту непосредственно на жесткий диск.

Недостаток воркеров

Из-за недостатка воркеров запросы забивают канал на веб-сервере, что приводит к тому, что сайт медленно открывается. 

Увидеть эту ошибку, можно проверив количество процессов PHP-fpm для обработки запросов. Для этого нужно провести базовую нагрузку от одного запроса в секунду до 1000 запросов в секунду. 

Результаты тестов при нагрузке от 1 до 1000 запросов
Результаты тестов при нагрузке от 1 до 1000 запросов
Результаты тестов при нагрузке от 1 до 1000 запросов
Результаты тестов при нагрузке от 1 до 1000 запросов

Скорее всего, в PHP-fpm вы увидите ошибки выполнения запросов: 110: Operation timed out. При этом не будет ни нагрузки на процессор, ни на память:

Самое вероятное решение такой проблемы — добавить воркеров для процессов PHP-fpm.

Синхронное использование внешних сервисов

Эта проблема является следствием ошибок логики кода, когда сайт взаимодействует с сервисом хранения логов, кэширования и гео-модулем или другим внешним сервисом. Как раз в одном из наших проектов клиент использовал внешний сервис GeoIP — базу данных для геолокации по IP-адресу.

После первой итерации тестирования мы увидели, что максимальный уровень загрузки при обращении к динамическому контенту не поднимается выше 30 RPS. А днем и того хуже – 5 RPS. За основу нагрузочного тестирования взяли лог доступа к серверу, а именно динамические PHP-страницы из него. При этом уже при 31 RPS скорость работы сайта значительно снижалась, но не наблюдалось нагрузки ни на веб-сервере, ни на сервере с базой данных. 

Сайт при 31 RPS 
Сайт при 31 RPS 

Одним из главных факторов, сдерживающих скорость работы сайта, являлось использование внешнего сервиса geoip:

05:09:07.227091 connect(23, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("208.95.112.1")}, 16) = -1 EINPROGRESS (Operation now in progress)

05:09:07.227236 poll([{fd=23, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=23, revents=POLLOUT}])

05:09:07.270599 getsockopt(23, SOL_SOCKET, SO_ERROR, [0], [4]) = 0

05:09:07.270726 fcntl(23, F_SETFL, O_RDWR) = 0

05:09:07.270899 getpeername(23, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("208.95.112.1")}, [16]) = 0

05:09:07.271123 access("/home/bitrix/www/bitrix/modules/main/lib/diag/logger.php", F_OK) = 0

05:09:07.271251 access("/home/bitrix/www/vendor/composer/../psr/log/Psr/Log/AbstractLogger.php", F_OK) = 0

05:09:07.271386 access("/home/bitrix/www/vendor/composer/../psr/log/Psr/Log/LoggerInterface.php", F_OK) = 0

05:09:07.271532 sendto(23, "GET /json/95.213.162.109?lang=ru HTTP/1.1\r\nHost: ip-api.com\r\nConnection: close\r\nAccept: */*\r\nAccept-Language: en\r\n\r\n", 116, MSG_DONTWAIT, NULL, 0) = 116

05:09:07.271652 poll([{fd=23, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)

05:09:07.271760 poll([{fd=23, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 0 (Timeout)

05:09:17.276580 close(23)               = 0

05:09:17.276878 select(21, [20], [20], NULL, {1, 0}) = 1 (out [20], left {0, 999997})

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

  1. Настроили новый сервер как новую ноду для масштабирования веб-воркеров.

  2. Настроили конфигурационные файлы.

  3. На новом сервере протестировали перенос сессий.

  4. Подготовили конфигурационные файлы к подключению новых нод.

В результате сайт стал выдерживать приблизительно 200 RPS: 

Интенсивная работа с жестким диском

Некоторые CMS и внешние сервисы склонны по умолчанию загружать файлы на HDD или хранить там кеш. Обращение к файлам, которые лежат на диске “подвешивает” текущие процессы и сильно загружает процессор. Есть и другой вариант этой проблемы — когда скорости чтения/записи на диск попросту не хватает для обслуживания большого количества запросов.

Например, мы сталкивались с тем, что при открытии клиентского сайта каждый раз открывалась база SypexGeoIP. И при каждом открытии код загружал файл с настройками в память целых 8 секунд:

начало обработки файла
05:18:32.835075 open("/data/local/php_interface/generated_files/SxGeoCity.dat", O_RDONLY|O_LARGEFILE) = 8

конец обработки файла:
05:18:40.530117 read(8

Статистика системных вызовов для одного fpm-процесса показывала, что более 90% времени процесс обрабатывал этот файл с настройками (вызовы munmap, read, mmap ). Из-за этого процессы fpm почти все висели в состоянии обработки базы и чрезмерно забивали канал на ЦПУ. 

Выглядело это вот так
Выглядело это вот так

Проблему решило отключение перманентной загрузки файла в память. Это строку: /data/local/modules/citfact.sitecore/lib/geoip/sxgeo.php в контейнере с fpm поменяли вот так:

define('SXGEO_MEMORY', 1); на define('SXGEO_MEMORY', 0);

Решив сложности с geo-ip, мы ещё раз проанализировали системные вызовы под нагрузкой. Теперь большую часть времени сайт стал заниматься доступом к файлам.

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
37.18    0.155432           6     23256      7107 access
29.44    0.123042           4     25276      3119 stat
  9.18    0.038387           5      7332       366 lstat
  4.77    0.019952          12      1624           poll
  4.72    0.019720          28       685           munmap

Помимо доступа к файлам ядра, Битрикс регулярно обращался к файлам изображений одного и того же товара и проверял их в разных размерах:

2491803 06:21:26.756402 access("/data/upload/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/Massimo_mini_Premier_10_0003.jpg", F_OK) = 0
2491803 06:21:26.756467 access("/data/upload/resize_cache/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/1506_1506_1/Massimo_mini_Premier_10_0003.jpg", F_OK) = 0
2491803 06:21:26.756519 access("/data/upload/resize_cache/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/1506_1506_1/Massimo_mini_Premier_10_0003.webp", F_OK) = 0
2491803 06:21:26.756562 access("/data/upload/resize_cache/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/1506_1506_1/Massimo_mini_Premier_10_0003.jpg", F_OK) = 0
2491803 06:21:26.756603 access("/data/upload/resize_cache/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/753_753_1/Massimo_mini_Premier_10_0003.jpg", F_OK) = 0
2491803 06:21:26.756643 access("/data/upload/resize_cache/iblock/dc2/o9va4u3l2ni02gfp0kz7l390eyduvi2j/753_753_1/Massimo_mini_Premier_10_0003.webp", F_OK) = 0


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

  1. Исправить логику работы кода,  чтобы при каждом открытии не проверялись сотни файлов. 

  2. Отказаться от NFS и перенести изображения на бэкенды — это снизит нагрузку на диски и ускорит время ответа сайта.

Пару слов о нагрузочном

Как видите, нагрузочное тестирование — это мощный инструмент, который помогает улучшить отказоустойчивость и стабильность любого сайта. Обычно мы рекомендуем проверить сервис, если вы ожидаете резкий всплеск трафика. Например, во время сезонных распродаж. В этот раз мы немного запоздали с этим советом, ведь «Черная пятница» идет своим ходом. Поэтому хотим пожелать всем удачи и стойкости под высокой нагрузкой, а если у вас возникли проблемы, которые не получается решить, вы знаете, куда обратиться.


Больше материалов о нагрузочном тестировании, советов, как жить, если ты DevOps и шишках, которые набили наши клиенты при работе с высоконагруженными сайтами, читайте в нашем ТГ-канале: https://t.me/itsumma

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