RE:NODE

Веб-хостинг12 мин чтения

Скорость WordPress и кэширование: что на самом деле влияет на TTFB

Кэш страниц, OPcache, объектный кэш и изображения: какие оптимизации WordPress меняют время до первого байта, какие ничего не меняют и как измерить и то и другое.

0 прочтений

Без кэша каждый просмотр страницы WordPress запускает PHP: загружается ядро, каждый активный плагин, выполняется от тридцати до ста запросов к базе, рендерится тема. Для обычного сайта это от 300 до 900 миллисекунд серверного времени, и всё повторяется для следующего посетителя, запросившего ту же самую страницу. Кэш целых страниц хранит готовый HTML и отдаёт его, не трогая PHP, что снижает время до первого байта примерно до 20-80 миллисекунд. Ничто другое из доступного вам не сравнится с этим.

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

Куда на самом деле уходит время#

Прежде чем что-либо менять, разделите загрузку страницы на часть, которой владеет сервер, и часть, которой владеет браузер. Первую половину curl даёт бесплатно:

bash
$ curl -o /dev/null -s -w "dns %{time_namelookup}  tcp %{time_connect}  \tls %{time_appconnect}  ttfb %{time_starttransfer}  total %{time_total}\n" \  https://example.com/
ФазаТипичное хорошее значениеЧто это значит
dns0.01-0.05 sЗапрос к резолверу, после первого запроса кэшируется
tcp0.02-0.10 sКруговой путь до сервера, по большей части расстояние
tls0.04-0.15 sРукопожатие, ещё один-два круговых пути
ttfb0.05-0.20 sВсё перечисленное выше плюс время на размышления сервера
totalменьше 1 sПлюс загрузка самого HTML

Вычтите tls из ttfb, и останется собственная работа сервера: PHP, база данных и файловая система. Только это число и меняет кэширование. Если ваш ttfb равен 0,6 с, а tls завершился на 0,12 с, у вас примерно полсекунды PHP, на которые можно наступать. Если ttfb равен 0,18 с, а страница всё равно кажется медленной, проблема в браузере, и никакой хостер её не исправит; эта граница подробнее проведена в статье TTFB, Core Web Vitals и хостинг.

Запустите запрос дважды. Второй скажет, работает ли кэш; первый покажет, что получает невезучий посетитель.

Кэш страниц - это большая часть выигрыша#

Кэш страниц хранит отрендеренный HTML для URL и отдаёт его напрямую при следующем запросе. Для блога, сайта-визитки или сайта с документацией это покрывает почти всех посетителей, потому что почти все посетители не вошли в систему и просят то, что уже просил кто-то другой.

В WordPress для этого есть встроенный хук. Строка define( 'WP_CACHE', true ); в wp-config.php заставляет ядро очень рано, раньше большей части фреймворка, загружать wp-content/advanced-cache.php, а плагин кэширования кладёт этот файл на место. Самые популярные - WP Super Cache, W3 Total Cache, Cache Enabler, LiteSpeed Cache (полезен только на сервере LiteSpeed) и WP Rocket. Выберите один. Два кэша страниц на одном сайте - классический способ показать панель администратора вошедшего пользователя всем посетителям.

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

  • Что обходит кэш. Любой запрос с cookie wordpress_logged_in_*, comment_author_*, wp-postpass_* или, в магазине, cookie корзины должен обслуживаться заново. Каждый серьёзный плагин делает это по умолчанию. Если сделано неверно, проявляется не медленность, а то, что один пользователь видит страницу другого.
  • Срок жизни кэша. От нескольких часов до суток подходит большинству сайтов. Короткие сроки превращаются в постоянно холодный кэш на сайте с малым трафиком, где каждая страница устаревает раньше, чем её запросят дважды.
  • Предзагрузка. Обход карты сайта для прогрева кэша после очистки. Полезна на сайте с сотнями страниц и умеренным трафиком; бессмысленна на загруженном, где посетители прогревают кэш за минуту.

Проверяйте снаружи, без входа в систему, в приватном окне или через curl -I. Большинство плагинов добавляют заголовок - x-cache: HIT, x-litespeed-cache: hit, комментарий внизу HTML. Если такой метки найти нельзя, сравните ttfb двух последовательных запросов к одному URL: у работающего кэша страниц виден очевидный обрыв.

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

OPcache#

PHP при каждом запросе компилирует исходный код в опкоды, если не включён OPcache, хранящий их в разделяемой памяти. WordPress с двадцатью плагинами - это тысячи файлов, поэтому это даёт от 30 до 50 процентов времени PHP на некэшированном запросе и не стоит ничего, кроме памяти.

php.ini
opcache.enable=1opcache.memory_consumption=192opcache.interned_strings_buffer=16opcache.max_accelerated_files=20000opcache.validate_timestamps=1opcache.revalidate_freq=2
НастройкаЗначение по умолчаниюЗачем менять
opcache.memory_consumption128 (МБ)Сайт с большим числом плагинов может заполнить 128 МБ; переполненный кэш молча перестаёт кэшировать
opcache.interned_strings_buffer8 (МБ)Повышается вместе с предыдущей на больших сайтах
opcache.max_accelerated_files10000WordPress и 30 плагинов могут превысить 10 000 файлов
opcache.validate_timestamps1Для WordPress оставьте включённым
opcache.revalidate_freq2 (секунды)Насколько устаревшим может отдаваться изменённый файл

Единственное, чему стоит сопротивляться, - opcache.validate_timestamps=0. Это верная настройка для приложения, развёртываемого из Git с последующим перезапуском, и неверная для WordPress, где обновления плагинов и ядра меняют файлы изнутри админки. При выключенной проверке меток времени такие обновления как будто ничего не делают, пока PHP не перезапущен, а наполовину применённое обновление - это сломанный сайт.

Проверяйте, не переполнен ли OPcache, а не предполагайте. opcache_get_status() в однострочном скрипте или страница состояния, которая есть в большинстве плагинов кэширования, показывает num_cached_keys против max_cached_keys, used_memory против free_memory и долю попаданий. Доля попаданий ниже примерно 95 процентов на устойчивой нагрузке означает, что кэш вытесняется, а значит, одно из первых двух чисел слишком мало.

Объектный кэш и база данных#

У WordPress есть внутренний объектный кэш, хранящий результаты запросов и вычисленные значения под ключами. По умолчанию он живёт в рамках запроса: создаётся в начале запроса и выбрасывается в конце, поэтому экономит только повторную работу внутри одной загрузки страницы. Чтобы он стал постоянным, нужен drop-in в wp-content/object-cache.php поверх Redis или Memcached, который устанавливается плагином вроде Redis Object Cache.

Честно оцените, доступно ли это вам. Нужен сервер Redis или Memcached, до которого дотянется процесс PHP, и расширение для общения с ним. Во многих общих и контейнерных хостингах нет ни того ни другого - управляемые базы RE:NODE это PostgreSQL и MongoDB, которыми WordPress не пользуется, - поэтому, если вы не можете указать на реальный экземпляр, постоянный объектный кэш вам не светит, и хорошо настроенный кэш страниц становится соразмерно важнее. Общее решение описано в статье Redis и когда он на самом деле нужен.

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

sql
SELECT option_name, LENGTH(option_value) AS bytesFROM wp_optionsWHERE autoload IN ('yes', 'on')ORDER BY bytes DESCLIMIT 20;

Конструкция IN нужна потому, что в WordPress 6.6 столбец autoload изменили с простых yes/no на набор, включающий on, off и auto, поэтому запрос, написанный под старые значения, молча вернёт пустоту на новой установке. Сложите столбец bytes: меньше нескольких сотен килобайт - здоровое состояние, больше мегабайта - налог на каждый просмотр страницы. Обычные виновники - плагин, хранящий большой кэшированный блок как автозагружаемую опцию, и годы осиротевших transient, оставленных плагинами, которые удалили без очистки.

Ревизии записей - другая лёгкая победа. Страница, отредактированная восемьдесят раз, несёт восемьдесят копий в wp_posts. Ограничьте их в wp-config.php, а не удаляйте вручную раз в год:

wp-config.php
define( 'WP_POST_REVISIONS', 10 );define( 'EMPTY_TRASH_DAYS', 14 );

Изображения - это большая часть байтов#

На типичной странице WordPress изображения составляют от 60 до 80 процентов передаваемого веса. Чтобы это исправить, сервер задействовать не нужно.

  • Загружайте разумные оригиналы. WordPress уменьшает всё шире 2560 пикселей и хранит оригинал рядом с суффиксом -scaled, поэтому фотография с телефона в 6000 пикселей стоит хранилища дважды и ничего не даёт. Уменьшайте до загрузки.
  • Используйте современный формат. WordPress принимает загрузки WebP с версии 5.8 и AVIF с версии 6.5. WebP обычно на 25-35 процентов меньше эквивалентного JPEG при том же визуальном качестве.
  • Ленивая загрузка уже включена. С версии 5.5 WordPress добавляет loading="lazy" к изображениям, а с версии 6.3 вместо этого помечает вероятное самое большое изображение как fetchpriority="high", что верно для Largest Contentful Paint. Плагин, который лениво загружает всё подряд, включая главное изображение, ухудшает LCP.
  • Перегенерируйте миниатюры после смены темы. Новая тема регистрирует другие размеры; без перегенерации браузеру отдаётся полноразмерный файл, уменьшенный средствами CSS.
  • Следите за хранилищем. Каждый зарегистрированный размер умножает каждую загрузку. Пять размеров плюс оригинал плюс уменьшенная копия - это семь файлов на одну фотографию, и медиатека - самый большой потребитель диска на большинстве тарифов для WordPress.

Клиентская часть обычно медленнее#

Когда время до первого байта меньше 200 миллисекунд, оставшиеся секунды принадлежат браузеру, и именно здесь живёт большая часть «медленного WordPress». Хостинг ничего из перечисленного ниже не меняет.

  • CSS и JavaScript, блокирующие отрисовку. Конструктор страниц, загружающий восемь таблиц стилей, jQuery и пять скриптов плагинов, прежде чем что-либо появится на экране.
  • Шрифты. Три семейства в четырёх начертаниях, каждое отдельной загрузкой; размещайте их у себя, делайте подмножества и задавайте font-display: swap.
  • Сторонние скрипты. Виджет чата, два тега аналитики, баннер согласия и пиксель. Каждый - это DNS-запрос, соединение и блокирующий скрипт, и они постоянно оказываются худшими виновниками по Interaction to Next Paint.
  • Слайдеры и видео в шапке. Крупные, выше сгиба и обычно декоративные.

Измеряйте эту половину инструментом браузера, а не через curl. Полевые данные в отчёте Core Web Vitals не совпадут с вашим лабораторным тестом, и учитываются именно полевые. Другая половина скорости клиентской части - заставить браузер хранить уже скачанное, чтобы повторные просмотры ничего не стоили, - разобрана в статье заголовки HTTP-кэширования простыми словами.

Рабочие процессы PHP и ёмкость, о которой никто не говорит#

Скорость под нагрузкой - не то же самое, что скорость в простое. У пула PHP-FPM фиксированное число рабочих процессов, и каждый обслуживает один запрос за раз. Поэтому арифметика беспощадна:

code
requests per second = workers / average request seconds

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

Число процессов ограничено памятью. Каждый рабочий процесс WordPress держит в памяти от 40 до 80 МБ, а иногда и больше с тяжёлой темой, так что 1 ГБ RAM поддерживает примерно от восьми до двенадцати процессов, когда веб-сервер и база получили свою долю. Значение pm.max_children выше того, что выдерживает память, не добавляет ёмкости, а превращает очередь в событие нехватки памяти. Эту арифметику как следует разбирает статья сколько RAM нужно WordPress, а настройки php.ini, которые важны описывают memory_limit - сторону того же вопроса, относящуюся к отдельному процессу.

Что меняет хостинг, а что нет#

Хостингу принадлежат четыре вещи, и стоит точно их назвать, потому что хостер не может продать вам более быструю тему.

  • Доля CPU определяет, как быстро выполняется один запрос PHP. Жёсткое ограничение CPU означает, что сервер на пределе работает медленно, а не ломается.
  • Память определяет, сколько запросов выполняется одновременно, через число рабочих процессов.
  • Диск важен меньше, чем думают, когда OPcache прогрет, но NVMe сокращает холодные чтения и делает запись в базу дешёвой.
  • Расстояние задаёт нижнюю границу для tcp и tls. Немецкий сервер, отвечающий посетителям в Бразилии, платит 200 миллисекунд за каждый круговой путь, что бы вы ни кэшировали. Это довод в пользу CDN перед сервером, а не другого хостера.

В RE:NODE тарифы для сайтов работают на хранилище NVMe на оборудовании в Германии, CPU ограничивается долей, купленной тарифом, а не заимствуется у соседей, а консоль показывает вывод самого веб-сервера, так что ошибка 500 видна без обращения в поддержку. Если сайт всё же достигает лимита памяти, контейнер останавливается и перезапускается начисто, а не уходит в swap, - это резко, но делает машину предсказуемой и ещё одна причина подбирать число процессов под тариф, а не под оптимизм. Что говорят графики памяти и CPU в панели, пока дело не дошло до этого, объясняет статья чтение графика нагрузки сервера.

FAQ#

Какой плагин кэширования выбрать?

Любой из основных, настроенный один раз и оставленный в покое. WP Super Cache и Cache Enabler проще всех, W3 Total Cache настраивается глубже всех и проще всех неверно настраивается, LiteSpeed Cache - лучший выбор, только если сервер действительно работает на LiteSpeed. Разница между двумя хорошо настроенными кэшами страниц невелика; разница между одним и отсутствием кэша огромна.

Заменяет ли CDN кэш страниц?

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

Почему сайт быстрый для меня и медленный для посетителей?

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

Есть ли смысл в объектном кэше без Redis?

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

Ускорит ли сайт больше RAM?

Только если её не хватает. Больше памяти даёт больше одновременных рабочих процессов PHP, поэтому она поднимает точку, в которой сайт замедляется под нагрузкой, но не ускоряет рендеринг одной страницы. Если медленно даже одному посетителю, ответы - OPcache, меньше запросов и более лёгкая тема.

Как найти плагин, который всё тормозит?

Установите Query Monitor, загрузите медленную страницу под администратором и прочтите количество запросов, самые медленные запросы и время на каждый хук. Он называет файл и плагин. Двадцать запросов на страницу - нормально, двести означает, что какой-то плагин делает глупости при каждом обращении.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000