Ваш сайт работал отлично, пока пользователей было мало. Но стоило прийти тысяче человек одновременно — и всё зависло. Знакомая история? В этой статье разберём, как заставить PHP-приложение летать даже при высокой нагрузке. Без магии, только проверенные приёмы.

Digital-студия WNDER

Почему PHP тормозит под нагрузкой

PHP — интерпретируемый язык. Каждый запрос заново выполняет скрипт, подключает файлы, создаёт соединения с базой. Когда запросов тысячи в секунду, накладные расходы становятся критичными.

Основные узкие места:

  • Медленные SQL-запросы без индексов.
  • Отсутствие кэширования.
  • Избыточная загрузка файлов и библиотек.
  • Синхронные операции, которые можно отложить.

Давайте разбираться по порядку.

1. Профилируйте, а не гадайте

Первое правило оптимизации: не трогайте код, пока не знаете, что именно тормозит. Используйте профайлеры — Xdebug, Blackfire или Tideways. Они покажут, какие функции съедают больше всего времени.

Пример простого профилирования с Xdebug:


// Включаем профайлер
xdebug_start_trace('/tmp/trace.log');

// Ваш код
$result = heavyFunction();

xdebug_stop_trace();

После анализа лога вы увидите, например, что 80% времени уходит на запрос к базе без индекса. Или на рекурсивную функцию, которую можно заменить циклом.

Правило: Сначала измерьте, потом оптимизируйте. Преждевременная оптимизация — корень всех зол.

2. Кэширование: ваш главный союзник

Кэш — это как холодильник. Вместо того чтобы каждый раз бежать в магазин (базу данных), вы достаёте готовое. Чем меньше запросов к БД, тем выше скорость.

Уровни кэширования:

Тип Что кэшируем Инструменты
OPcache Скомпилированный байт-код PHP Встроен в PHP
Данные Результаты запросов, объекты Redis, Memcached
Страницы Готовый HTML Varnish, Nginx FastCGI Cache

Пример кэширования в Redis:


$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'user:123:profile';
$data = $redis->get($key);

if (!$data) {
    $data = $db->query('SELECT * FROM users WHERE id = 123')->fetch();
    $redis->setex($key, 3600, serialize($data));
} else {
    $data = unserialize($data);
}
Лайфхак: Для OPcache установите opcache.validate_timestamps=0 на продакшене. Это отключит проверку изменений файлов и даст прирост до 20%.

3. Оптимизация работы с базой данных

База — это сердце приложения. Если она тормозит, всё остальное бесполезно.

Что делать:

  • Индексируйте поля, по которым идёт поиск и сортировка.
  • Избегайте SELECT * — берите только нужные колонки.
  • Используйте подготовленные запросы (prepared statements) для безопасности и скорости.
  • Разбивайте сложные запросы на простые.
  • Для больших таблиц применяйте партиционирование.

Пример плохого и хорошего запроса:


// Плохо: каждый раз новый запрос, нет индекса
$users = $db->query("SELECT * FROM users WHERE status = 'active'");

// Хорошо: используем подготовленный запрос и индекс
$stmt = $db->prepare("SELECT id, name FROM users WHERE status = ?");
$stmt->execute(['active']);
$users = $stmt->fetchAll();

Не забывайте про соединения. Постоянные соединения (persistent connections) экономят время на установку связи с БД.

4. Асинхронность и очереди

Не всё нужно делать сразу. Отправка email, генерация отчётов, обработка изображений — эти задачи можно отложить. Используйте очереди: RabbitMQ, Redis, Beanstalkd.

Пример с Redis в качестве очереди:


// Постановка задачи в очередь
$redis->lPush('email_queue', json_encode(['to' => 'user@example.com', 'subject' => 'Hello']));

// В отдельном воркере
while (true) {
    $job = $redis->brPop('email_queue', 30);
    if ($job) {
        $data = json_decode($job[1], true);
        sendEmail($data['to'], $data['subject']);
    }
}

Это разгружает веб-сервер и ускоряет ответ пользователю.

5. Настройка сервера и PHP-FPM

PHP-FPM — это менеджер процессов, который обрабатывает запросы. Его настройки напрямую влияют на производительность.

Ключевые параметры:

Параметр Рекомендация
pm dynamic (для переменной нагрузки) или static (для стабильной)
pm.max_children Зависит от RAM: (доступная память) / (память на процесс)
pm.start_servers Обычно 2-4
pm.min_spare_servers 1-2
pm.max_spare_servers 3-5

Также включите OPcache и настройте его правильно:


; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
Правило: Не ставьте pm.max_children наугад. Если процессов больше, чем доступной памяти, сервер уйдёт в своп и умрёт.

6. Код: пишите эффективно

Даже с хорошим сервером плохой код всё испортит. Вот несколько быстрых правил:

  • Используйте встроенные функции вместо самописных циклов.
  • Избегайте лишних включений файлов (require_once внутри циклов).
  • Применяйте генераторы для обработки больших массивов.
  • Отключайте ненужные модули PHP.

Пример с генератором:


function readLargeFile($file) {
    $handle = fopen($file, 'r');
    while (!feof($handle)) {
        yield fgets($handle);
    }
    fclose($handle);
}

foreach (readLargeFile('huge.log') as $line) {
    // Обрабатываем строку, не загружая весь файл в память
}

Что в итоге

Оптимизация PHP для высоких нагрузок — это комплекс мер: профилирование, кэширование, грамотная работа с БД, асинхронность, настройка сервера и чистый код. Начните с узких мест, внедряйте постепенно. И помните: лучшая оптимизация — та, которая не ломает функциональность.

Применяйте эти советы, и ваше приложение скажет вам спасибо. Удачи!

Студия WNDER