С чего начать оптимизацию производительности PHP-сайта

Оптимизацию PHP-сайта лучше начинать не с переписывания кода, а с замеров. Медленный сайт может тормозить из-за SQL-запросов, тяжёлых изображений, внешних API, отсутствия кэша, неудачной конфигурации PHP-FPM или просто из-за одного неудачного цикла в шаблоне. Без измерений легко потратить день не туда.

Сначала зафиксировать проблему

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

  • страницы сайта — смотреть TTFB, общий вес и запросы браузера;
  • backend-операции — смотреть логи, SQL и профилирование;
  • cron и импорты — замерять время выполнения команды;
  • API — отдельно проверять внешние запросы и таймауты.
time php yii some/command

Если проблема проявляется только иногда, стоит посмотреть нагрузку на сервер и медленные запросы в базе. Иногда “сайт тормозит” на самом деле означает, что ночью запускается тяжёлый импорт.

Проверить логи и ошибки

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

tail -n 200 runtime/logs/app.log
tail -n 200 /var/log/nginx/error.log

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

SQL-запросы: частая причина тормозов

В Yii2 удобно случайно получить N+1: список товаров загружается одним запросом, а для каждой строки отдельно тянутся категории, цены или остатки. На десяти товарах это незаметно, на тысяче — уже проблема.

$products = Product::find()
    ->with(['category', 'prices'])
    ->where(['status' => 'A'])
    ->all();

with() не является универсальным лекарством, но часто помогает убрать десятки повторяющихся запросов. Для тяжёлых списков стоит смотреть фактический SQL и план выполнения.

EXPLAIN SELECT * FROM products WHERE status = 'A' ORDER BY created_at DESC;

Если фильтр используется постоянно, возможно, нужен индекс. Но индекс добавляют под конкретный запрос, а не “на всякий случай”. Лишние индексы замедляют запись и занимают место.

Кэшировать не всё подряд

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

$key = ['active-categories'];
$categories = Yii::$app->cache->get($key);

if ($categories === false) {
    $categories = Category::find()
        ->where(['status' => 'A'])
        ->orderBy(['sort_order' => SORT_ASC])
        ->asArray()
        ->all();

    Yii::$app->cache->set($key, $categories, 3600);
}

Для Yii2 отдельно полезно помнить про кэш схемы таблиц. На рабочих проектах это снижает лишние обращения к базе.

'db' => [
    'class' => 'yii\db\Connection',
    'enableSchemaCache' => true,
    'schemaCacheDuration' => 3600,
    'schemaCache' => 'cache',
],

Изображения и фронтенд

Иногда PHP уже отвечает быстро, но страница всё равно кажется медленной из-за изображений. Баннер в несколько мегабайт, неподходящий формат или отсутствие lazy loading легко портят впечатление.

  • проверить вес главных изображений;
  • использовать реальные размеры, а не уменьшение огромной картинки через CSS;
  • включить lazy loading для изображений ниже первого экрана;
  • убрать лишние сторонние скрипты;
  • проверить кеширование статических файлов.
<img src="/uploads/product.webp" width="600" height="400" loading="lazy" alt="">

Composer и автозагрузка

На production-сервере зависимости обычно устанавливают без dev-пакетов и с оптимизированной автозагрузкой. Это не решит проблему плохих запросов, но является нормальной базовой настройкой.

composer install --no-dev --prefer-dist --optimize-autoloader

Если проект старый, перед обновлением Composer стоит быть осторожнее. Иногда одна новая версия пакета тянет за собой несовместимость. Для рабочих серверов лучше использовать lock-файл.

PHP-FPM и opcache

Без opcache PHP каждый раз заново читает и компилирует файлы. Для нормального production-окружения opcache должен быть включён.

php -i | grep opcache.enable

Пример базовых настроек:

opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2

На некоторых проектах validate_timestamps отключают и сбрасывают opcache при деплое. Это быстрее, но требует дисциплины. Если забыть сброс, сервер может продолжить выполнять старый код.

Рабочий порядок оптимизации

  1. Зафиксировать медленный сценарий.
  2. Измерить время ответа и посмотреть логи.
  3. Проверить количество SQL-запросов.
  4. Найти N+1 и медленные запросы.
  5. Проверить кэширование повторяющихся данных.
  6. Проверить вес изображений и сторонние скрипты.
  7. Проверить Composer, opcache и PHP-FPM.
  8. Повторить замер после каждой правки.

Главное правило простое: одна правка — один повторный замер. Если менять всё сразу, потом сложно понять, что действительно помогло, а что просто совпало по времени.

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

Пока нет комментариев. Будьте первым!