«Хороший сайт делается для людей, а поисковики приходят следом»

— из принципов редакции SeoLiga.ru
Главная » Статьи » Веб-программирование » Кэширование страниц и ускорение PHP-сайта

Кэширование страниц и ускорение PHP-сайта

Скорость загрузки — не роскошь, а условие выживания сайта. Посетители уходят, если страница открывается дольше нескольких секунд, а поисковые системы учитывают скорость при ранжировании. Для PHP-проектов типичная причина медлительности одна: на каждый запрос скрипт заново подключает файлы, обращается к базе, собирает страницу из кусочков и отдаёт результат, который почти не меняется. Эту работу можно сделать один раз и запомнить. Именно это и есть кэширование. Разберём, где и как его применять.

Где теряется время

Прежде чем оптимизировать, нужно измерить. Скорость отклика сервера и скорость отображения у пользователя — разные вещи. Выделим составляющие:

  • время ответа сервера (генерация страницы PHP, обращения к базе);
  • передача данных по сети (размер страницы, сжатие);
  • загрузка внешних ресурсов (стили, скрипты, изображения, шрифты);
  • отрисовка в браузере (тяжёлый JavaScript, блокирующие ресурсы).

Для серверной части включите профилирование: логируйте время выполнения скриптов, считайте число запросов к базе на страницу, используйте отладочные панели вашего фреймворка. Часто выясняется, что три-четыре запроса съедают девять десятых времени.

Первый уровень: кэш опкодов

PHP — интерпретируемый язык: при каждом запросе исходный код разбирается и превращается в промежуточное представление. Расширение OPcache сохраняет результат компиляции в памяти, и повторного разбора не происходит. Это самый дешёвый способ ускорения: он включается в настройках и не требует изменений кода.

; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=60

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

Второй уровень: кэширование данных

Самая дорогая операция — запрос к базе. Если результат не меняется ежесекундно, его стоит хранить в быстром хранилище. Варианты: файлы, общая память через APCu, внешние хранилища вроде Redis или Memcached. Для небольшого сайта на обычном хостинге часто достаточно файлового кэша.

Ниже — простой и рабочий пример класса с хранением данных в файлах.

<?php
class FileCache
{
    private string $dir;

    public function __construct(string $dir)
    {
        $this->dir = rtrim($dir, '/');
        if (!is_dir($this->dir)) {
            mkdir($this->dir, 0775, true);
        }
    }

    private function path(string $key): string
    {
        return $this->dir . '/' . md5($key) . '.cache';
    }

    public function get(string $key, int $ttl)
    {
        $file = $this->path($key);
        if (is_file($file) && (time() - filemtime($file)) < $ttl) {
            return unserialize(file_get_contents($file));
        }
        return null;
    }

    public function set(string $key, $value): void
    {
        $tmp = $this->path($key) . '.tmp' . getmypid();
        file_put_contents($tmp, serialize($value), LOCK_EX);
        rename($tmp, $this->path($key));
    }
}

Использовать его просто: сначала спрашиваем кэш, а при промахе идём в базу и сохраняем результат.

$cache = new FileCache(__DIR__ . '/cache');
$news = $cache->get('last_news', 300);
if ($news === null) {
    $news = $db->query('SELECT id, title FROM news ORDER BY id DESC LIMIT 10')->fetchAll();
    $cache->set('last_news', $news);
}

Обратите внимание на запись через временный файл и переименование: так читатель никогда не увидит наполовину записанный файл. Это мелочь, которая избавляет от странных ошибок под нагрузкой.

Третий уровень: кэш целых страниц

Для страниц, одинаковых для всех посетителей (статьи, карточки, разделы каталога), можно хранить готовый HTML. Тогда при повторном запросе PHP почти не работает: отдаёт файл и завершает выполнение. Выигрыш — в разы.

<?php
$key = $_SERVER['REQUEST_URI'];
$file = __DIR__ . '/pagecache/' . md5($key) . '.html';

if (is_file($file) && time() - filemtime($file) < 600) {
    readfile($file);
    exit;
}

ob_start();
// ... обычная генерация страницы ...
$html = ob_get_contents();
ob_end_flush();
file_put_contents($file, $html, LOCK_EX);

Условия, при которых так делать можно: запрос методом GET, пользователь не авторизован, нет персональных блоков. Если на странице есть корзина, имя посетителя или токены форм, такие фрагменты придётся подгружать отдельно или исключать страницу из кэша.

Инвалидация: когда сбрасывать кэш

Старую поговорку об инвалидации кэша знают все программисты: это одна из двух самых трудных задач. На практике достаточно трёх подходов.

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

Следите за эффектом «лавины»: когда кэш популярной страницы истёк, сто посетителей одновременно бросаются её пересчитывать. Решением служит блокировка: первый процесс обновляет, остальные временно получают старую версию.

Кэширование в браузере и сжатие

Статические ресурсы — картинки, стили, скрипты, шрифты — не должны скачиваться заново при каждом визите. Настройте заголовки Cache-Control и срок жизни, а для обновления файлов добавляйте к имени версию или хеш содержимого.

# .htaccess
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType text/css "access plus 1 week"
  ExpiresByType application/javascript "access plus 1 week"
</IfModule>
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

Включите сжатие текстовых ответов и современные форматы изображений. Объём передаваемых данных сократится в несколько раз, что особенно важно для мобильных пользователей.

Оптимизация запросов к базе

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

  1. Добавьте индексы на поля, по которым идёт фильтрация и сортировка.
  2. Проверьте план выполнения запросов командой EXPLAIN и найдите полные просмотры таблиц.
  3. Избегайте запросов в цикле: собирайте идентификаторы и делайте выборку одним запросом.
  4. Выбирайте только нужные столбцы вместо звёздочки.
  5. Для больших списков используйте постраничную выборку с лимитом, а не загрузку всего.
  6. Включайте журнал медленных запросов и регулярно его просматривайте.

Другие приёмы ускорения

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

Типичные ошибки

Первая — кэшировать всё подряд без разбора, включая страницы с персональными данными: посетитель может увидеть чужую корзину. Вторая — забыть про сброс кэша при обновлении контента, из-за чего редактор правит текст, а на сайте ничего не меняется. Третья — хранить кэш в общедоступной папке без защиты, где его можно скачать напрямую. Четвёртая — оптимизировать наугад, не измеряя до и после. Всегда сравнивайте цифры: время ответа, число запросов, нагрузку на процессор.

Заключение

Ускорение PHP-сайта — это последовательная работа по уровням: включить OPcache, убрать лишние запросы, закэшировать данные и готовые страницы, настроить кэш в браузере и сжатие. Начинайте с измерений, делайте по одному изменению за раз и проверяйте результат. Даже базовый набор из этих мер способен сократить время загрузки в несколько раз и заметно снизить нагрузку на хостинг, а значит, повысить удобство посетителей и позиции в поиске.

ИБ
Ирина Белова
Автор SeoLiga.ru. Пишет о практике: что работает на реальных проектах, а что лучше обойти стороной. Хотите предложить свою статью? Загляните в раздел «Авторам».