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

Digital-студия WNDER

Почему PHP тормозит: взгляд под капот

Каждый раз, когда пользователь заходит на сайт, PHP-скрипт компилируется заново. Представьте, что вы каждый день учите один и тот же стих, вместо того чтобы просто его рассказать. Это и есть основная проблема «из коробки».

Но это только верхушка айсберга. Вот основные пожиратели ресурсов:

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

Давайте пройдемся по каждому пункту и превратим черепаху в гепарда.

OPcache: первое правило оптимизации

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

Проверить, включен ли он, можно функцией opcache_get_status(). Если нет — срочно включайте в php.ini.

// Пример проверки статуса OPcache
$status = opcache_get_status();
if ($status === false) {
    echo 'OPcache выключен. Вы теряете производительность!';
} else {
    echo 'OPcache работает. Использовано памяти: ' . $status['memory_usage']['used_memory'] . ' байт';
}
Правило: Всегда устанавливайте opcache.validate_timestamps = 0 на продакшене. Да, придется сбрасывать кэш при деплое, но вы получите прирост скорости до 50%.

База данных: где прячется дьявол

Чаще всего PHP-приложение тормозит не из-за кода, а из-за базы. Классическая ошибка — делать запросы внутри цикла. Это называется проблемой N+1.

Вот как делать не надо:

// Плохой пример: запрос в цикле
$users = $db->query('SELECT id, name FROM users');
foreach ($users as $user) {
    // Для каждого пользователя делаем отдельный запрос
    $orders = $db->query('SELECT * FROM orders WHERE user_id = ' . $user['id']);
    // ... обработка
}

А вот как надо — одним запросом:

// Хороший пример: один запрос с JOIN
$sql = 'SELECT u.id, u.name, o.amount 
        FROM users u 
        LEFT JOIN orders o ON o.user_id = u.id';
$result = $db->query($sql);
// ... обработка

Кроме того, всегда используйте подготовленные выражения (prepared statements). Это не только безопасно, но и быстрее, так как база кэширует план запроса.

Проблема Решение Прирост
Запросы в цикле JOIN или IN До 10 раз
SELECT * Только нужные поля До 30%
Отсутствие индексов Индексы на WHERE и JOIN До 100 раз

Сессии и файлы: не пишите на диск без нужды

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

Решение — перенести сессии в Redis или Memcached. Это займет пару минут, а скорость вырастет в разы.

// Настройка сессий в Redis
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
session_start();

То же самое касается кэша. Если вы кэшируете данные в файлы, при высокой нагрузке диск будет тормозить. Используйте in-memory хранилища.

Лайфхак: Для хранения кэша используйте Redis с настройкой maxmemory-policy allkeys-lru. Это автоматически удалит старые данные, когда память закончится.

Код: пишите меньше, делайте быстрее

Иногда оптимизация — это просто удаление лишнего. Вот несколько приемов, которые дают мгновенный эффект:

  • Используйте встроенные функции PHP вместо самописных. Они написаны на C и работают быстрее.
  • Избегайте магических методов __get и __set — они замедляют работу.
  • Не используйте @ для подавления ошибок. Это очень дорогостоящая операция.
  • Замените count() в цикле на заранее вычисленное значение.

Пример оптимизации цикла:

// Плохо: count() вызывается на каждой итерации
for ($i = 0; $i < count($array); $i++) {
    // ...
}

// Хорошо: count() вызывается один раз
$count = count($array);
for ($i = 0; $i < $count; $i++) {
    // ...
}

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

// Генератор для чтения большого файла
function readLines($file) {
    $handle = fopen($file, 'r');
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
    fclose($handle);
}

foreach (readLines('huge_file.txt') as $line) {
    // Обработка строки без загрузки всего файла в память
}

Инфраструктура: когда код уже не спасает

Бывает, что код идеален, но сервер не справляется. Тогда в игру вступает инфраструктура.

Первое — используйте PHP-FPM с правильными настройками пула. Не ставьте слишком много процессов, иначе они начнут драться за CPU.

Второе — балансировка нагрузки. Nginx + несколько серверов с PHP-FPM творят чудеса.

Третье — кэширование на уровне HTTP. Отдавайте статику через Nginx, используйте CDN для картинок и скриптов.

Совет: Настройте pm.max_requests в PHP-FPM. Это количество запросов, после которого процесс перезапускается. Помогает избежать утечек памяти. Значение 500-1000 — хороший старт.

И не забывайте про мониторинг. Без него вы не узнаете, где узкое место. Используйте New Relic, Blackfire или хотя бы Xdebug для профилирования.

Что в итоге

Оптимизация PHP — это не магия, а последовательность шагов. Сначала включаем OPcache, потом переносим сессии в Redis, затем чистим запросы к базе. Если этого мало — беремся за код и инфраструктуру.

Главное правило: не оптимизируйте то, что не тормозит. Сначала измерьте, потом улучшайте. И помните, что самый быстрый код — тот, который не выполняется.

Студия WNDER