Ваш сайт летал на локалке, но упал, как только пришла первая тысяча пользователей? Знакомая история. PHP — язык простой и доступный, но именно эта простота часто играет злую шутку с производительностью. Давайте разберемся, как заставить приложение работать быстро, даже когда нагрузка зашкаливает.
Почему 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 хранилища.
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



