📜 ⬆️ ⬇️

Page caching in WordPress

image

Recently, quite a lot of posts on this topic appeared on Habré, but in essence they can be called: "Look, I put the Varnish / W3 Total Cache and keep a million requests on the" Hello world "page." This article is designed more for geeks who want to know how it all works and write your own plugin for page caching.

What for?


The standard question that arises from each developer before creating a bike of an existing functionality. Indeed, there are a lot of ready-made plug-ins and many of them are quite high-quality, but you need to understand that first of all they are designed for static blogs. What to do if you have a non-standard WordPress site?

Let's get started


What tools does WordPress provide us?


As everyone knows, this CMS makes it easy to extend its functionality with plugins, but not everyone knows that there are several types of plugins:

')

advanced-cache.php


In order for this plugin to start functioning, it must be placed in the wp-content directory, and the line added to wp-config.php :
define('WP_CACHE', true); 

If you look at the WordPress code, you can see that this script is loaded at an early stage of loading the platform.
 // wp-settings.php:63 // For an advanced caching plugin to use. Uses a static drop-in because you would only want one. if ( WP_CACHE ) WP_DEBUG ? include( WP_CONTENT_DIR . '/advanced-cache.php' ) : @include( WP_CONTENT_DIR . '/advanced-cache.php' ); 

Also, after loading the kernel, the CMS will attempt to call the wp_cache_postload () function, but about it later.
 // wp-settings.php:226 if ( WP_CACHE && function_exists( 'wp_cache_postload' ) ) wp_cache_postload(); 


Storage


It is best to use fast storages for storing caches, since the speed at which content is retrieved from the cache directly depends on their speed. I would not advise using MySql or the file system; Memcached, Redis, or other storages that use RAM will do a better job.

I personally like Redis, as it is quite easy to use, it has good read / write speeds and as a nice bonus it saves a copy of the data to the hard disk, which will allow not to lose information when the server is restarted.
 $redis = new Redis(); //    $redis->connect( 'localhost' ); //   $value   $key   $timeout $redis->set( $key, $value, $timeout ); //     $key $redis->get( $key ); //     $key $redis->del( $key ); 

Of course, this is not a complete list of methods, the entire list of APIs can be studied on the official website , but for most tasks this is sufficient.

If the site uses a pumped object cache ( object-cache.php ), then it makes sense to use its API:
 wp_cache_set( $key, $value, $group, $timeout ); wp_cache_get( $key, $group ); wp_cache_delete( $key, $group ); 


Simplest page caching


The code is purposely simplified, many checks are removed, so as not to confuse the reader with unnecessary constructions and to focus on the logic of the caching itself. In the file advanced-cache.php we register:
 //      ,     , //          wp_start_object_cache(); //   //    URL  $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ); //       if( $data = wp_cache_get( $key, 'advanced-cache' ) ) { //   ,      $html = $data['html']; die($html); } //   ,   //        if( ! is_admin() ) { //    ob_start( function( $html ) use( $key ) { $data = [ 'html' => $html, 'created' => current_time('mysql'), 'execute_time' => timer_stop(), ]; //         10  wp_cache_set($key, $data, 'advanced-cache', MINUTE_IN_SECONDS * 10); return $html; }); } 


Everything, you have received the simplest working page cache, now we will consider each section in more detail.

Key creation
 $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ); 
In this case, the key is the URL of the page. Using the $ _SERVER global variable and hashing cannot be called a best practice, but for a simple example it will do. I advise you to add separating sections of the line as "host:" and "uri:", since it is convenient to use them in regular expressions. For example, get all the keys for a specific host:
 $keys = $redis->keys( 'host:' . md5( 'site.com' ) . ':*' ); 

Issuance from cache
 //       if( $data = wp_cache_get( $key, 'advanced-cache' ) ) { //   ,      $html = $data['html']; die($html); } 
Everything is simple, if the cache has already been created, then give it to the user and complete the execution.

Save to cache
PHP function ob_start intercepts all subsequent output to the buffer and allows you to process it at the end of the script. In simple words, we get all the site content in the $ html variable.
 ob_start( function( $html ) { // $html - HTML    return $html; } 

Next, save the data to the cache:
 $data = [ 'html' => $html, 'created' => current_time('mysql'), 'execute_time' => timer_stop(), ]; wp_cache_set($key, $data, 'advanced-cache', MINUTE_IN_SECONDS * 10); 

It makes sense to save not only HTML, but also other useful information: cache creation time, and so on. I highly recommend saving HTTP headers, at least Content-Type and sending them when it is issued from the cache.

Improving


In the example above, we used the is_admin () function to exclude admin panel caching, but this method is not very practical for two reasons:

The best solution for a simple site would not use the cache for logged in users (administrators) at all. Since advanced-cache.php is executed before the kernel is fully loaded, we cannot use the is_user_logged_in () function, but we can determine if cookie authentication is in place (as you know, WordPress does not use a session).
 //   cookie wordpress_logged_in_* $is_logged = count( preg_grep( '/wordpress_logged_in_/', array_keys( $_COOKIE ) ) ) > 0; //       if( ! $is_logged ) { ob_start( function( $html ) use( $key ) { // .... return $html; }); } 


Complicate the task


Suppose our site gives a different content for users from different regions or countries. In this case, the cache key should be not only the URL of the page, but also the region:
 $region = get_regeon_by_client_ip( $_SERVER['REMOTE_ADDR'] ); $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ) . ':region:' . md5( $region ); 

By this principle, we can form different caches for different groups of users by any parameters.

wp_cache_postload ()


This function is called after the kernel is loaded and it is also convenient to use it in some cases.
From experience I will say that this option works much more stable:
 function wp_cache_postload() { add_action( 'wp', function () { ob_start( function( $html ) { // ... return $html; }); }, 0); } 

At the time of the wp_cache_postload () call, the add_action function already exists and can be used.

There are situations when you need data that is impossible to get from cookies, IP and other resources available at the initialization stage to generate a cache key. For example, you need to generate an individual cache for each user (sometimes it makes sense).
 function wp_cache_postload() { $key = 'host:' . md5( $_SERVER['HTTP_HOST'] ) . ':uri:' . md5( $_SERVER['REQUEST_URI'] ) . ':user:' . get_current_user_id(); if( $data = wp_cache_get( $key, 'advanced-cache' ) ) { $html = $data['html']; die($html); } add_action( 'wp', function () { ob_start( function( $html ) { // ... return $html; }); }, 0); } 

As you can see in the example, all the logic is placed in the body of wp_cache_postload, and all the functions of the platform are already available, including get_current_user_id () . This option is a bit slower than the previous one, but we have endless possibilities for fine-tuning the page cache.

What should not be forgotten


  1. These examples are very simplified, if you use them in your projects - do not be lazy to add conditions for caching:
    • GET requests only
    • only if there are no errors on the page
    • only if there is no set-cookie
    • only if the status is 200 or 301
  2. The effectiveness of the cache depends on its lifetime. Increasing $ timeout, bother to think about invalidating the cache when data changes.
  3. WP Cron is launched later than advanced-cache.php, it may simply not work with a high cachehit.


Conclusion


There is nothing difficult in writing your own page caching. Of course, this does not make sense for a typical site, but if you have created a monster - this material should be useful.

Source: https://habr.com/ru/post/251189/


All Articles