Оптимизация сайта под высокие нагрузки (php,nginx, mysql). Какие критерии доработок? Что необходимо сделать в первую очередь?
Доброго времени суток!
У нас имеется сайт с большой базой пользователей (700 тысяч). Сайт предназначен для записи на мероприятия и одна из его особенностей - БД с кучей полей у некоторых таблиц. Сайт написан на php, использует сервер nginx и СУБД MySQL
С увеличением количества пользователей, готовых участвовать в наших онлайн мероприятиях мы столкнулись с тем, что сайт зависает и жестко тупит, пока есть очередь из запросов пользователей: достаточно 2000-4000 человек, заходящих в течение 20 минут на сайт, чтобы его "положить на лопатки". Пользователи выполняют стандартный набор действий: авторизация, регистрация, запись на мероприятие. Сервер имеет следующую конфигурацию: 24 vCPU intel core ice lake, 128 Gb ОЗУ и SSD хранилище данных. БД, сервер и файлы сайта находятся на одной виртуальной машине, всё это добро размещено на яндекс облаке.
Встал вопрос в оптимизации сайта под высокие нагрузки
Что необходимо сделать в первую очередь в плане оптимизации?
Разработчики данного сайта предложили осуществить следующие работы:
1. Нагрузочные тесты и увеличение ресурсов сервера,
2. Подключение облачной сети быстрой доставки контента (CDN), для оптимизации доставки статического содержимого конечному пользователю и снижении нагрузки на целевой веб-сервер nginx.
3. Развертывание и подключение быстрого кеширующего сервиса REDIS в облачной среде для большей надежности и оптимизации.
4. Вынос MySQL в облачный сервер
Но мы сомневаемся, что этого будет достаточно... какие критерии к оптимизации сайта можно выставить, чтобы проверить выполненные работы и что ещё можно включить в данные работы, если предложения выше подходят для решения?
Дополнительно:
> Какие критерии доработок? Что необходимо сделать в первую очередь?
В первую очередь нужно бы выяснить что именно тормозит.
Включите лог медленных запросов.
На каждый сделайте explain, создайте нужные индексы.
Ответы:
Как уже заметили коллеги - первое что нужно выяснить - что тормозит.
Спойлер: 90% что проблема в запросах к бд, так как сам по себе код в большинстве случаев банально отсылает запросы и принимает их результат, выводя его пользователю в несложных действиях. В редких случаях - запросы идут к внешним тормозящим сервисам, например к фейсбуку или другим СС. И крайне редко тормозит сам код, но тут уже опять же - 90% что это некомпетентность разраба, например, не способного построить нормальный запрос и сортирующий данные от бд в коде...
Что конкретно надо делать:
1) Взять за шкирку самого разбирающегося в коде (если таковой существует в проекте), и дать задание описать базовый стек вызовов, если это какой-то самопис, или сразу расставить код замера времени с метками в случае понятной архитектуры. Что то типа такого(класс - древний костыль, но работает, так что матом не ругаться):
Class Timer.php:
class Timer { static $start; static $end; static $marks = []; static $formats = [1=>'']; static function init(){ if(empty(self::$start)) self::$start = microtime(true); } static function setMark($markName = ''){ $time = microtime(true); if($markName == '')$markName = $time; $data['name'] = $markName; $data['time'] = $time; $res['time'] = $time; if(count(self::$marks) > 1)$res['diff'] = $time - self::$marks[count(self::$marks)-2]['time']; else $res['diff'] = 0; $data['diff'] = $res['diff']; self::$marks[] = $data; return $res; } static function timeFormat($number,$format = ''){ if(empty($format)) $format = 3; return number_format ($number,$format,'.',''); } static function report(){ self::$end = microtime(true); self::$marks['start'] = self::$start; self::$marks['end'] = self::$end; self::$marks['all_time'] = self::$end - self::$start; if(!empty(self::$marks)) return self::$marks; } } |
class Timer { static $start; static $end; static $marks = []; static $formats = [1=>'']; static function init(){ if(empty(self::$start)) self::$start = microtime(true); } static function setMark($markName = ''){ $time = microtime(true); if($markName == '')$markName = $time; $data['name'] = $markName; $data['time'] = $time; $res['time'] = $time; if(count(self::$marks) > 1)$res['diff'] = $time - self::$marks[count(self::$marks)-2]['time']; else $res['diff'] = 0; $data['diff'] = $res['diff']; self::$marks[] = $data; return $res; } static function timeFormat($number,$format = ''){ if(empty($format)) $format = 3; return number_format ($number,$format,'.',''); } static function report(){ self::$end = microtime(true); self::$marks['start'] = self::$start; self::$marks['end'] = self::$end; self::$marks['all_time'] = self::$end - self::$start; if(!empty(self::$marks)) return self::$marks; } }
In code:
Timer::init() //some code block 1 Timer::setMark('after block 1'); //some code block 2 Timer::setMark('after block 2'); ... //some code block n Timer::setMark('after block n'); //near end of code Timer::setMark('end'); var_dump(Timer::report()); exit; |
Timer::init() //some code block 1 Timer::setMark('after block 1'); //some code block 2 Timer::setMark('after block 2'); ... //some code block n Timer::setMark('after block n'); //near end of code Timer::setMark('end'); var_dump(Timer::report()); exit;
2) Смотрите на блоки жрущие время, делите их до атомарных операций путем деления блоков пополам таймерами.
3) Смотрите что там происходит - оптимизируете*. И так по кругу.
4) Профит.
* Оптимизация
Запросы:
1) Смотреть план запроса (use explain, Luke!).
2) Расставить индексы которых явно не хватает
3) Смотреть не вызывается ли 50 запросов в цикле? Если да - выписать пенделя писавшему, затем переписать в 1 запрос с нормальным джоином.
Код:
Иногда запрос сложно оптимизировать, он вытаскивает много данных, хотя эти данные не часто обновляются. Такие запросы нужно кешировать, для чего используют быстрые ин-мемори хранилища типа редис или мемкеш. В крайнем случае в файлах...
Чаще всего код тормозит на регулярках, хотя "хороший" программист может придумать и более креативные способы погреть процессор.
Что нужно сделать обязательно кроме тестов и как тогда лучше спрашивать с разработчиков, если они предлагают размытые предложения? Хочется понять в какую сторону копать
Бить палкой не вариант? Тогда берите других, эти испортились. Если разработчик не знает как выявить узкие места кода - нахрена он нужен? Код написать сегодня любой чат может... Ну, на крайняк дайте им вышеприведенный вариант решения проблемы...
PS: Кстати, сервер может банально не выдерживать наплыв сетевых соединений, пните адимна, пусть глянет логи.
PPS:
достаточно 2000-4000 человек, заходящих в течение 20 минут на сайт
это равномерные 3-4 рпс, ну или пусть в пике 50 рпс, должно держать даже на несложной конфигурации... Копайте код.
Первым делом необходимо выполнить замеры и найти, что конкретно тормозит. Судя по предложениям разработчиков, подозреваю, что стоит ещё и их уволить.
- Почему ж разработчиков за такие предложения стоит уволить?
- Дмитрий, Ну потому то как минимум вынос мускла с локальной машины в облако увеличит время обработки запросов (с нуля, так как локальная база до сетевых задержек, так как удаленная база).
Хотя нагрузочные тесты конечно не повредят.
- Дмитрий, если предложения переданы именно так, как были озвучены, то они выдают некомпетентность. CDN вообще не при чём, если с сервака не тащат статику гигабитами, что вряд ли. Развернуть Redis и MySQL на отдельных серваках в той же среде - разумное решение, но пункты сформулированы очень странно. Как бы их авторы ещё больше тормозов на сетевых издержках не наловили. Упоминание о том, что у некоторых таблиц куча полей может служить сигналом, что нормализация БД отсутствует, что обычно соседствует с неумением писать оптимальные запросы. Наконец, 2000-4000 человек за 20 минут - это 2-4 запроса в секунду. Если сайт на серваке со 128 гигами памяти и 24-я ядрами ложится от 3 RPS, то руки разработчика кривые чуть более, чем полностью.
- CityCat4, зависит от пинга. он пока не известен.
- Сергей Горностаев, Ну наловят они сетевые задержки без попытки хотя бы пингануть облако сказать нельзя.
Смотря какие запросы и какие требования были к таблицам. Денормализация имеет место быть как решение для высокой нагрузки - другое дело что никогда не слышал что бы от 3 запросов в секунду.Ну и самый главный вопрос - не понятно перед каким количеством разработчиков и с каким бюджетом поставлена задача. Если там 2 человека которыми сказали сделать быстро и дешево - они предложили ровно все возможные решения
- Дмитрий, мы конечно не знаем всех деталей, а потом мои предположения могут быть ошибочными, но в свои фрилансерские времена я интернет-магазины, с очевидно на много более сложной функциональностью, писал в одиночку за пару недель. а они потом легко обрабатывали 300 rps с соразмерными объёмами данных.
- dkis, Прежде всего для ответа необходимо знать детали вашего бизнес-процесса. Если это просто запись на мероприятия, то обеспечить тысячу rps можно без особых ухищрений парой самых дешёвых VPS. Если там какие-нибудь хитрости есть, типа составления оптимального расписания - это уже совсем другая история будет. А дальше профилирование, профилирование и ещё раз профилирование. В зависимости от его результатов оптимизация запросов к БД, оптимизация кода, кэширование, горизонтальное масштабирование и т.д. и т.п. Волшебного рецепта подходящего всегда и всем просто не существует, у каждого проекта собственный путь к высокой эффективности, который лучше прокладывать под руководством человека с опытом в высоких нагрузках.
- Сергей Горностаев, ну как бы весь вопрос в функционале и о том куда летит нагрузка. и каков бюджет и вагон всякого такого. без контекста - говорить что разработчиков на мыло, весьма преждевременно
- dkis, закажите аудит у какой нибудь конторы
- dkis,
точной информации по распределению нагрузки к сожалению нет
Это у вас ее нет, у админа есть лог реквестов к вебсерверу, исходя из которого легко считается рпс.
- dkis, Я извиняюсь, но у вас какой-то шаманский подход к программированию и разработке. "Что надо сделать чтобы сервер заработал по другому, шаманы слушают духов, трясут бубном, но все без толку...". Сегодня весь сегмент программирования ОЧЕНЬ прозрачный, говорю это как человек разрабатывавший код в период когда перл был вершиной эволюции веб разработки, а писали все в редакторе дос навигатора, ну или более продвинутые в фар менеджере... Сегодня можно посмотреть и узнать буквально все, включая какие айпи адреса спамят вас ддосом, а для профайлинга кода придумана куча прибамбасов. Для скюель сервера есть слоулог, если нужно более глубокий анализ - ставим перкону или что-то похожее... Любой нормальный современный движок для веба имеет встроенное логирование, или на крайняк поддерживает установку подходящего модуля... Короче, задача решаемая, все инструменты уже существуют, нужно только время и прямые руки.
Вы делаете какую то хрень без диагностики
Для начала тупо топ в линуксе что в такие моменты жрет больше всего ресурсов и уже это как то пытаться диагностировать и решать
А так вы на уровне у нас ни одного админа и программиста нет мы сделаем какую то хрень по советам интернета
У вас довольно мощный сервер и его должно хватать на порядок больше нагрузки.
CDN не сильно поможет, так как нагрузка на чтение ни о чем, все и так должно работать. Nginx точно не узкое место. Поэтому я бы первым делом включил журналирование медленных запросов в mysql и посмотрел бы что там в топе. А так первый комментарий хороший, поможет найти проблему. Но в целом я бы посоветовал завести метрики и графану, и добавить метрики по операциям в коде и бд. Чтобы иметь представление на будущее. Не знаю насколько сложно это делать в php.
Скорее всего эта проблема связана с БД. Вероятно есть SQL запросы, которые плохо оптимизированы под эту задачу. В этом случае тогда нужно оптимизировать запросы, возможно еще нужно создать индексы, возможно еще произвести тюнинг MySQL, увеличить некоторые буферы и другие настройки.
Для оптимизации нужно:
1. Нужно включить логирование медленных запросов. Много раз использовал эту методику для выявления узких запросов.
После уже можно понять какие php файлы являются проблемными, и их исправить.
Логирование медленных запросов включается так в разделе [mysqld] в файле my.cnf:
slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 |
slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1
Анализ лог файла делается так:
mysqldumpslow -s c -t 20 slow.log.1 > results.log
2. Также может помочь утилита mysqltuner.pl. Многократно ее использовал для определения проблемных запросов, индексов, которые нужно добавить в таблицы, и других проблем с MySQL/MariaDB.
3. Можно вручную провести анализ запросов при помощи EXPLAIN.
4. Можно сделать замеры выполнения кода, как вручную, через логирование блоков кода, так и через XDebug. И выявить долго выполняющиеся блоки, после этого оптимизировать их.
5. Кроме того нужно посмотреть процессы на сервере, которые занимают много процессорного времени и памяти, это можно сделать через atop или другие подобные утилиты. Можно на основании этого оптимизировать программный код и службы.
6. То что MySQL и сам сервер находятся на виртуальной машине это тормозит работу сервера, виртуальные ноды в любом случае работают медленнее, чем физический сервер. Рекомендую перенести это все на физический сервер с NVMe дисками, с современным процессором и ОЗУ не менее 128 Гб.
Могу помочь настроить логирование, анализ этого и выявить узкие места.
Подозреваю что это банальная очередь запросов, php-fpm как-то настраивался? Nginx в данной цепочке является прокси-сервером, он ожидает ответ от php-fpm.
Думаю необходимо в корне пересматривать структуру проекта.
На каждую сессию сервер резервирует 2 Мб.
То есть на 4000 пользователей это 8 Гб.
И из реальной жизненной ситуации - сервер на 166 Pentium' е, с 256 Мб памяти под FreeBSD и всем "стеком LAMP" обслуживал до 10 запросов в секунду, а здесь с ресурсами на несколько порядков выше 4 запроса в секунду ложат на лопатки.
Первое что можно сделать быстро.
Это индексы, как увидеть тормозящие запросы можно посмотреть тут https://electrictoolbox.com/show-running-queries-mysql/
Далее, оптимизация самой базы. Вот тут есть референсы
https://highload.today/mycnfexample-ram-16/
https://highload.today/index-php-2009-04-23-optima...
https://losst.ru/optimizatsiya-proizvoditelnosti-mysql
https://habr.com/ru/company/oleg-bunin/blog/328458/
И третье: это https://kafka.apache.org/. Строить балансировку для MySQL
В первую очередь смотрим логи nginx. После этого в 80% случаях вам понадобится изменить
- limit.conf
- sysctl.conf
- пулы php-fpm
- и nginx.conf надеюсь тоже у вас не по умолчанию
А после этого, помолясь, смотрим логи Mysql. Если ошибок нет, включаем просмотр медленных запросов.
Часто, но скорей всего не в вашем случае, помогает установка redis. Но redis тоже настраивать нужно уметь и можно сделать просто хуже.
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
Для оптимизации сайта под высокие нагрузки, необходимо выполнить ряд доработок и настроек как на стороне сервера (nginx, PHP, MySQL), так и на стороне приложения.
1. **Настройка Nginx:**
- Установка и настройка Nginx как веб-сервера.
- Настройка параметров воркеров, соединений, буферов и кэширования.
- Включение gzip-сжатия для уменьшения размера передаваемых данных.
- Настройка кэширования статических файлов, чтобы уменьшить нагрузку на сервер.
2. **Настройка PHP:**
- Установка последней версии PHP и оптимизация его настроек.
- Использование опкод-кэширования (например, OPCache) для ускорения выполнения PHP-скриптов.
- Настройка параметров PHP-FPM для оптимального распределения ресурсов.
- Оптимизация кода приложения: избегание лишних запросов к БД, оптимизация циклов и запросов.
3. **Настройка MySQL:**
- Установка и настройка MySQL для оптимальной производительности.
- Создание индексов на часто используемые поля в таблицах.
- Оптимизация запросов к базе данных, использование правильных инструкций JOIN.
- Масштабирование базы данных: использование кластеризации, шардинга или репликации для улучшения производительности.
4. **Кэширование:**
- Использование кэширования на уровне приложения (например, Memcached или Redis) для хранения часто запрашиваемых данных.
- Кэширование результатов запросов к базе данных для уменьшения нагрузки на сервер.
5. **Мониторинг и отладка:**
- Установка мониторинга производительности сервера (например, New Relic) для отслеживания узких мест и проблем.
- Логирование ошибок и запросов для быстрого выявления проблем и их решения.
При оптимизации сайта под высокие нагрузки необходимо учитывать все вышеперечисленные аспекты, проводить тестирование производительности и постоянно улучшать настройки и код приложения. В первую очередь рекомендуется начать с настройки кэширования, оптимизации базы данных и мониторинга производительности, так как это может дать наибольший эффект в сокращении нагрузки на сервер.