Скорость загрузки — не роскошь, а условие выживания сайта. Посетители уходят, если страница открывается дольше нескольких секунд, а поисковые системы учитывают скорость при ранжировании. Для 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>
Включите сжатие текстовых ответов и современные форматы изображений. Объём передаваемых данных сократится в несколько раз, что особенно важно для мобильных пользователей.
Оптимизация запросов к базе
Кэш не заменяет здравый смысл. Если запрос выполняется секундами, он будет тормозить при каждом промахе кэша и забивать сервер при сбросе.
- Добавьте индексы на поля, по которым идёт фильтрация и сортировка.
- Проверьте план выполнения запросов командой EXPLAIN и найдите полные просмотры таблиц.
- Избегайте запросов в цикле: собирайте идентификаторы и делайте выборку одним запросом.
- Выбирайте только нужные столбцы вместо звёздочки.
- Для больших списков используйте постраничную выборку с лимитом, а не загрузку всего.
- Включайте журнал медленных запросов и регулярно его просматривайте.
Другие приёмы ускорения
- Отложенная загрузка изображений ниже первого экрана.
- Объединение и минификация стилей и скриптов, перенос неважных скриптов на конец страницы.
- Использование сети доставки контента для статических файлов.
- Обновление версии PHP: новые ветки заметно быстрее старых.
- Отказ от лишних плагинов и библиотек, особенно в системах управления контентом.
- Удаление неиспользуемого кода и сторонних виджетов, которые тянут внешние запросы.
Типичные ошибки
Первая — кэшировать всё подряд без разбора, включая страницы с персональными данными: посетитель может увидеть чужую корзину. Вторая — забыть про сброс кэша при обновлении контента, из-за чего редактор правит текст, а на сайте ничего не меняется. Третья — хранить кэш в общедоступной папке без защиты, где его можно скачать напрямую. Четвёртая — оптимизировать наугад, не измеряя до и после. Всегда сравнивайте цифры: время ответа, число запросов, нагрузку на процессор.
Заключение
Ускорение PHP-сайта — это последовательная работа по уровням: включить OPcache, убрать лишние запросы, закэшировать данные и готовые страницы, настроить кэш в браузере и сжатие. Начинайте с измерений, делайте по одному изменению за раз и проверяйте результат. Даже базовый набор из этих мер способен сократить время загрузки в несколько раз и заметно снизить нагрузку на хостинг, а значит, повысить удобство посетителей и позиции в поиске.