Transients API: Часть 2. Используем transients для улучшения производительности произвольных запросов

Дата публикации:Март 2, 2014

Как я отметил во введении, я узнал о transients слишком поздно: я занимался созданием сайта для клиента. Сайт был посвящен суперкомпьютерам: он существовал практически с появления интернета, и потому на нем было очень много контента. Ранее они сидели на своей собственной CMS, достаточно ужасной и неудобной, и в итоге решили перейти на WordPress.

Они настаивали на том, чтобы скопировать существующую разметку и контент и перенести все это на «рельсы» WordPress, поэтому я начал с создания произвольной темы, произвольных типов записей и таксономий. Их главная страница, как и в случае со многими новостными/журнальными сайтами, имела массу различных областей контента, выводящих свежие новости, мультимедиа, разные возможности и подкасты с разных участков сайта. Таким образом, файл home.php оказался забит разными произвольными запросами, которые происходили через WP_Query для получения разнообразной информации.

В процессе тестирования все шло прекрасно, поэтому мы решили перейти к тестовому сайту, а затем и к запуску ресурса. После того, как сайт оказался онлайн и просуществовал так некоторое время, его трафик стал медленно увеличиваться, однако при публикации новой статьи или обновления сайта сайт переставал работать, выдавая пустую страницу (никаких сообщений об ошибках, ничего). Я стал думать над тем, что могло приводить к такой ошибке. После длительного поиска и устранения различных проблем, с помощью Query Monitor я случайно обнаружил, что при загрузке каждой страницы происходит более 400 запросов к базе данных. Вы представляете себе эту цифру? 400!!! Я был удивлен, как сайт вообще оставался онлайн (спасибо хостингу WP Engine). Большинство этих запросов (более 190) шли от темы, как результат запуска нескольких разных WP_Query. Затем уже шли дополнительные запросы на получение мета-информации для возвращенных записей.

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

Изменение моего WP_Query

Файл home.php уже был замусорен разными экземплярами WP_Query: мне нужно было просто модифицировать их при помощи transients. Сделать это оказалось достаточно легко, особенно когда я получил первый опыт работы с transients.

Вот пример одного из многочисленных экземпляров WP_Query, которые присутствовали в моем home.php:

<?php // IN THE SPOTLIGHT QUERY
$pttimestamp = time() + get_option('gmt_offset') * 60*60;
$my_query = new WP_Query( array(
	'post_type' => 'spotlight',
	'posts_per_page' => 1,
	'post__not_in' => $do_not_duplicate,
	'meta_query' => array(
		array(
			'key' => '_hpc_spotlight_end_time',
			'value' => $pttimestamp,
			'compare' => '>'
		)
	)
) );
if( have_posts() ) { // HIDE SECTION IF NO CURRENT ITS FEATURE ?>
	// LOOP GOES HERE: NOT IMPORTANT TO EXAMPLE
<?php } ?>

Это – достаточно стандартное использование WP_Query. Здесь происходит поиск последних опубликованных записей типа spotlight, которых нет в моем массиве постов, чтобы они не дублировались на главной странице, и для которых метаданные со сроком истечения записей не были заданы. Цикл, следующий за запросом, был опущен в моем примере, поскольку он не так важен для применения transients.

Вот измененный запрос, который я рассмотрю строка за строкой ниже:

<?php // IN THE SPOTLIGHT QUERY
if( false === ( $its_query = get_transient( 'its_query' ) ) ) {
	$pttimestamp = time() + get_option('gmt_offset') * 60*60;
	$its_query = new WP_Query( array(
		'post_type' => 'spotlight',
		'posts_per_page' => 1,
    		'post__not_in' => $do_not_duplicate,
		'meta_query' => array(
			array(
				'key' => '_hpc_spotlight_end_time',
				'value' => $pttimestamp,
				'compare' => '>'
			)
		)
	) );
	set_transient( 'its_query', $its_query, 60*60*4 );
}
if( have_posts() ) { // HIDE SECTION IF NO CURRENT ITS FEATURE ?>
	// LOOP GOES HERE: NOT IMPORTANT TO EXAMPLE
<?php } ?>

Исходный запрос (строки 2-14) существует в том же самом виде в новом фрагменте кода (строки 3-15), за исключением более специфичного имени переменной (просто для очистки кода). Реальное различие в коде – строки по обе стороны от фактического запроса.

Сразу перед запросом, во второй строке, я совершаю два действия: сначала я помещаю в переменную $its_query значение transient с тем же именем, используя get_transient(). Затем я проверяю, содержит ли переменная какую-либо информацию. Если она пустая – либо потому что transient впервые создан, либо потому что данные были просрочены и удалены, — то запускается WP_Query для получения новой информации, сохраняемой в transient.

Как только запрос был выполнен (строки 3-15), последние данные сохраняются в transient its_query для передачи их в следующий раз, когда тот же самый запрос будет выполнен (строка 16). При сохранении используется set_transient(). Обратите внимание, что я установил время истечения как 4 часа (60 секунд * 60 минут * 4 часа) – об этом я еще скажу пару слов позже.

Для чего мы храним данные

Теперь мы можем использовать эти данные в цикле точно так же, как мы обычно это делаем. Я выполнил запрос и сохранил информацию в transient, чтобы в следующие четыре часа любой аналогичный запрос смог взять информацию из transient (как это указано в строке 2), вместо того чтобы снова совершать запрос.

Даже если эта страница будет загружаться один раз в минуту (примерно 1440 просмотров страниц/день), то WP_Query будет запущен 6 раз вместо 1440 за день. Умножьте это на все WP_Query, которые могут быть у вас на отдельной странице, и вы поймете, в чем состоит преимущество использования transients.

Данные должны быть актуальными

В нашем новом запросе мы задаем время истечения для данных, равное 4 часам. Однако что произойдет, если новая запись была опубликована, и мы не хотим ждать 4 часа, чтобы она была включена в данный запрос (что особенно важно для новостных сайтов, таких как тот, с которым я работал). Время истечения задается скорее с целью подстраховки – это максимальный период, во время которого данные должны оставаться неизменными до своего обновления.

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

Чтобы реализовать это, я написал новую функцию. В данном примере, поскольку запросы содержатся в моих файлах темы (и зависят от моей темы), приемлемо было бы поместить эту функцию в файл functions.php, хотя лично я обычно писал для этого функциональный плагин, чтобы сохранить свой код в чистоте. Вот, что я сделал:

<?php // IN THE SPOTLIGHT TRANSIENTS
 
function hpc_delete_its_transients() {
	global $post;
	if( $post->post_type == 'spotlight' ) {
		delete_transient( 'its_query' );
	}
}
 
add_action( 'save_post', 'hpc_delete_its_transients' );

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

Функция проверяет, задавался ли для сохраненного поста требуемый тип записей (строки 3-5), поскольку нет смысла удалять для записи данные transient, если пост был создан в другом типе записей и никоим образом не влияет на данный запрос.

Если тип записей был именно тот, который нам требуется, то на 6 строке мы используем функцию delete_transient() для удаления transient из базы данных. В итоге данные transient будут обновлены в следующий раз, когда они потребуются.

Заключение

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

Я настоятельно рекомендую разработчикам использовать transients в своей обычной практике. Чем быстрее вы изучите transients, тем лучше будет для вас. Вам не придется сталкиваться со сложными проблемами в разработке.

Источник: doitwithwp.com

Поделиться

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.