WordPress 6.0 is on the horizon, and we couldn’t be more excited! Not only because of the massive performance opportunities this release will bring, but because a number of our fantastic XWPeople were directly involved in its creation.
Here’s your one-stop guide for the features our team helped bring to life, with some top tips and recommendations to help you get the most from the latest installment of WordPress Core.
Contributing to WordPress Core performance is one of our top priorities at XWP. In fact, It’s a huge part of our cultural DNA, and a key part of our mission to build a better web. That’s why we’re so active at community events and sponsor contributions to the WordPress ecosystem.
Want to learn more? Read ‘The 10 (Really 11) Reasons I Joined XWP‘ from our Engineering Director Stéphane Boisvert.
Caching Improvements in WordPress 6.0
As part of the release of WordPress 6.0, the Performance team has been working on several improvements to the core— including a few new clever additions to the WordPress caching API.
Batch API Methods for Cache Operations (wp_cache_*_multiple
)
The function wp_cache_get_multiple()
was added in WordPress 5.5. This allowed for multiple cache keys to be collected in just one request. To complete this API, a full CRUD was needed and has been added via the following functions:
wp_cache_add_multiple
wp_cache_set_multiple
wp_cache_delete_multiple
All of these functions accept an array of data to be passed so that multiple cache objects can be created, edited, or deleted in a single cache call.
In WordPress core, these are just wrappers for core functions to allow multiple keys to be passed in one function call, but this would also allow object caching drop-in developers to implement them if their back-end supports it.
Example Usage of wp_cache_add_multiple( $data, $group = '', $expire = 0 )
$data
: Array of key and value pairs to be added.$group
: Optional. String. Where the cache contents are grouped. Default ”.$expire
: Optional. Int. When to expire the cache in seconds. Default 0 (no expiration).
wp_cache_add_multiple( array( 'foo1' => 'value1', 'foo2' => 'value2' ), 'group1' );
Example Usage of wp_cache_delete_multiple( $data, $group = '' )
$data
: Array of keys to be deleted.$group
: Optional. String. Where the cache contents are grouped. Default ”.
wp_cache_delete_multiple( array( 'foo1', 'foo2' ), 'group1' );
Example Usage of wp_cache_set_multiple( $data, $group = '', $expire = 0 )
$data
: Array of key and value pairs to be set.$group
: Optional. String. Where the cache contents are grouped. Default ”.$expire
: Optional. Int. When to expire the cache in seconds. Default 0 (no expiration).
wp_cache_set_multiple( array( 'foo1' => 'value1', 'foo2' => 'value2' ), 'group1' );
With these additions, some additional core refactoring has been done to utilize these new functions. See more details in Trac #55029.
Allowing Runtime Cache to be Flushed
As discussed in Performance issue #81 and Trac #55080, Core needed a way to allow users to flush the runtime (in-memory) cache without flushing the entire persistent cache.
This feature was often requested for instances where long-running processes such as Action Scheduler or WP-CLI are run.
Example usage of wp_cache_flush_runtime()
$counter = 0;
foreach ( $posts as $post ) {
wp_insert_post( $post );
if ( 100 === $counter ) {
wp_cache_flush_runtime();
$counter = 0;
}
$counter++;
}
The above example would reset the runtime cache after 100 posts are inserted into the database.
Dev notes on WordPress 6.0 Caching Improvements courtesy of our Senior Backend Engineer, Manuel Rodriguez (furi3r on WordPress.org)
Changes to the do_parse_request
Filter in WordPress 6.0
Prior to the WordPress 6.0 release plugin and theme developers used the do_parse_request
filter to hot-wire requests and hook in early to render custom pages. Post queries that weren’t needed and 404 lookups were still run.
This resulted in unnecessary SQL queries running on these requests.
The Addition of a Return Value
Example of 'do_parse_request'
in WordPress 6.0
In the simplest filter:
add_filter( 'do_parse_request', '__return_false' );
But you might want to check for a parameter before returning:
function myplugin_img_tag_add_border_color( $filtered_image, $context, $attachment_id ) {
$style = 'border-color: #cccccc;';
$filtered_image = str_replace( '<img ', '<img style="' . $style . '" ', $filtered_image );
return $filtered_image;
}
add_filter( 'wp_content_img_tag', 'myplugin_img_tag_add_border_color', 10, 3 );
Dev notes on do_parse_request in WordPress 6.0 courtesy of our WordPress Engineer, Paul Bearne (pbearne on WordPress.org)
New Filter to Modify Content Images in WordPress 6.0
WordPress 6.0 introduces a new filter wp_content_img_tag
to allow adjustment of images in a blob of HTML content, most prominently post content via the_content
filter.
WordPress 5.5 originally introduced the wp_filter_content_tags()
function to modify certain elements, primarily images, in a blob of HTML content.
Prior to the WordPress 6.0 release, it was not possible to alter these image tags without duplicating the complex regex logic in the wp_filter_content_tags()
function. This increased complexity and overhead. The new wp_content_img_tag
filter solves this problem.
How to Use wp_content_img_tag
The new wp_content_img_tag
filter passes the following parameters:
- string
$filtered_image
: The full img tag with attributes that will replace the source image tag. - string
$context
: Additional context, like the current filter name or the function name from where this was called. - int
$attachment_id
: The image attachment ID. May be 0 in case the image is not an attachment.
The filter must return a string which will then replace the img
tag passed to the filter.
Adding a Border-Color Style Attribute to Every Image Tag Using wp_filter_content_tags()
Here is an example in which the new filter is used to add a border-color style attribute to every image tag in the content:
function myplugin_img_tag_add_border_color( $filtered_image, $context, $attachment_id ) {
$style = 'border-color: #cccccc;';
$filtered_image = str_replace( '<img ', '<img style="' . $style . '" ', $filtered_image );
return $filtered_image;
}
add_filter( 'wp_content_img_tag', 'myplugin_img_tag_add_border_color', 10, 3 );
The wp_filter_content_tags()
function was originally introduced to facilitate lazy-loading support for images and has since become the standard approach for modifying in-content images for various performance enhancements.
The new wp_content_img_tag
filter expands these capabilities by giving control to plugin developers to add their own customizations.
For more context on the new filter, implementation see trac #55347.
Dev notes on do_parse_request in WordPress 6.0 courtesy of our WordPress Engineer, Paul Bearne (pbearne on WordPress.org)
Taxonomy performance improvements in WordPress 6.0
There are many term queries on the average page load and improving these improves the performance of WordPress in general.
Improvement to Term Query Caching.
Queries run by WP_Term_Query
have been cached since 4.6. The way these caches are primed and handled has been improved in WordPress 6.0.
Removing Cache Limit
Prior to WordPress 6.0, term query caches were limited to a 24-hour period for those using persistent object caching. This limitation is now removed, so if caches are not invalidated, it means that term query should cache much longer.
For inactive sites or overnight, caches should remain primed, which should improve site performance.
For more information, see trac #54511.
Term Query Cache Only Caches the Term ID
Term query caches have been changed so that instead of caching the whole term object, now only the term IDs are cached. This means that the value stored in cache will be much smaller (in terms of memory) and will not fill up memory in session or persistent object cache.
Once all the IDs for the terms are loaded, the _prime_term_cache
function is called. This loads into memory terms that are not already in cache. If the term is already in memory, then it is not loaded again, which is a performance benefit.
On an average page load, a term may be requested multiple times, like in the case of a tag archive page. Early in the page load, get_term_by
, will prime the term cache, and all other calls, such as get_the_terms
, will have the term already primed in page memory. This results in most cases in fewer and smaller queries that are run against the term table.
For more information, see trac #37189.
Improved Term Query Cache Key Generation
Previously, similar term queries that have similar cache keys would result in basically the same query being run twice on a single page load. Because all queries now only get the term ID and store it in cache (see above), the cache exactly the same.
For example, you create a call to get_terms
where you request all categories and return only the field slug. If you do the same query and request only the name, then this would hit the same cache.
Another improvement is the handling of ambiguous parameters that can be passed to WP_Term_Query
. Fields like slug that can be either a string or an array are now converted to always be an array, meaning that the likelihood of reusing a cache is higher as the cache key is the same, no matter which type of parameter is passed.
For more information, see #55352.
Improve Performance for Navigation Menu Items
Convert wp_get_nav_menu_items to use a taxonomy query
This replaces usage of get_objects_in_term
function with a simple taxonomy query. This replacement converts the use of two queries to get the menu items to use one simple query. This saves one query for each menu requested and adds consistency.
For more information, see trac #55372.
Prime all term and posts caches in wp_get_nav_menu_items
The wp_get_nav_menu_items
function now calls _prime_term_cache
and _prime_post_cache
for all objects linked to menu items.
If a menu contains a list of categories and pages, all the related objects are now primed in two cache calls (one for terms and one for posts). This will result in far fewer requests to the database and cache.
For more information, see trac #55428.
Convert term_exists to use get_terms
The function term_exists
has now been converted to use get_terms
( WP_Term_Query
) internally replacing raw uncached database queries. This function was one of the last places to perform raw queries to the terms
table in the database. Using the get_terms
function has a number of key benefits, including:
- Consistency with other core functions like
get_term_by
- The ability to filter the results.
- Results of
get_terms
are cached
term_exists
is designed for back-end use and is mostly used in core functions designed to write data to the term
table. However, term_exists
can and is used by some theme and plugin developers. This results in raw uncachable and unfilterable queries being run on the front-end of a site.
For those that need to ensure that term_exists
is getting an uncached result, there are two ways to do this:
- Using the new
term_exists_default_query_args
filter
$callback = function ( $args ) {
$args['cache_domain'] = microtime();
};
add_filter( 'term_exists_default_query_args', $callback );
$check = term_exists( 123, 'category' );
remove_filter( 'term_exists_default_query_args', $callback );
2. Using wp_suspend_cache_invalidation
wp_suspend_cache_invalidation( true );
$check = term_exists( 123, 'category' );
wp_suspend_cache_invalidation( false );
For more information, see trac #36949.
Add a limit to taxonomy queries
The WP_Tax_Query
class is used in WP_Query
to limit queries by term. Under the hood, WP_Tax_Query
uses WP_Term_Query
, which means that WP_Tax_Query
will get the benefits of the cache improvements documented above.
The WP_Tax_Query
run, which transforms term slugs/names into term IDs to be queried, now has a limit added to it.
This query limit improves the performance of the query, as well as improves the likelihood of an existing cache query to continue to exist in the object cache.
For example, a standard tag archive calls get_term_by
and primes the cache. By the time it gets to the WP_Tax_Query
, that query is being loaded from cache. This removes one query per tag archive page.
For more information, see #55360.
Dev notes on taxonomy improvements in WordPress 6.0 courtesy of our Senior Engineer and Architect, Jonny Harris (Spacedmonkey on WordPress.org)
Storing File Size as Part of an Image’s Metadata in WordPress 6.0
In #49412 a tweak was made to the upload process. When files are uploaded to the WordPress media library, the file’s size is now stored as part of the metadata. This means that when calling the wp_get_attachment_metadata
function, developers will easily be able to receive the file size of a selected image without having to call the php function filesize.
This change is especially useful in situations when media is being offloaded to a third party, such as cloud services like Amazon’s S3 and Google cloud storage, or a network share like NFS. This is because a call to get the file’s size can result in slow calls to an external server.
Along with saving the file size to the attachment’s metadata, a new function and filter have been added. The new wp_filesize
function is useful when storing attachments on a third-party source. It means that value can be filtered, preventing a possibility of a slow call to an external server.
Dev notes on media metadata in WordPress 6.0 courtesy of our Senior Engineer and Architect, Jonny Harris (Spacedmonkey on WordPress.org)
Performance Increases For Sites With Large User Counts (Now Also Available on Single Site)
In WordPress 6.0, websites with more than 10,000 users will see improvements in handling users. Prior to changes in #38741, sites with more than 10,000 users would suffer from slow page loading time in the user and post list screens.
The user screen was slow because a complex SQL query was run to get the number of users for each role. This sometimes resulted in page timeout or failures to load. On the post edit screen, a dropdown of authors in the quick edit option used to change the post’s author. On large sites, this results in thousands of options rendered on the page, which creates a large amount of markup and causes timeouts and slowdowns.
Introducing Large Sites
The idea of a large network in multisite configurations of WordPress has existed since WordPress 3.0. A network is considered large when it has over 10,000 users. When marked as such, any real time calculation of the number of users is skipped and all counts of users are deferred to scheduled (cron) events.
This idea of a large network has now been ported to single site installs of WordPress. Any site with more than 10,000 users is marked as large. Once this threshold is met, real time counts, such as ones noted above, no longer occur on every page load.
This means that when viewing user lists, the count next to each user role will be missing. This massively improves the performance of the page load and prevents slow database queries causing PHP timeout errors, which in turn helps ensure the user list renders every time on large sites.
This change includes some new functions:
get_user_count
Now available for both single- and multi-site installations. For sites with over 10,000 users recounting is performed twice a day by a scheduled event.wp_update_user_counts
Perform a user count and cache the result in the user_count site option.wp_is_large_user_count
Determines whether the site has a large number of users. The default threshold of 10,000 users can be modified using a filter of the same name.
Dev notes on media metadata in WordPress 6.0 courtesy of our Senior Engineer and Architect, Jonny Harris (Spacedmonkey on WordPress.org)
WP_User_Query now accepts fields options in WordPress 6.0
Prior to the WordPress 6.0 release the function WP_User_Query would only accept ID
and all_with_meta
/all
in the fields parameter.
New Options for Field Parameters
You can now pass any of these options in the field parameter and get the associated values back
‘ID’
‘display_name’
‘user_login’
‘user_pass’
‘user_nicename’
‘user_email’
‘user_url’
‘user_registered’
‘user_activation_key”
‘user_status’
and for a multi site, also:‘spam’
‘deleted’
As well as the following return WP_user objects in an array
‘all’ – default,
‘all_with_meta’
An Example of Field Parameters in WordPress 6.0
Code like this:
$users = get_users( ['fields' => 'display_name'] );
Will now return values like this:
Array (
[0] => Jack
[1] => jb
[2] => momo
[3] => Roberta
)
Dev notes on do_parse_request in WordPress 6.0 courtesy of our WordPress Engineer, Paul Bearne (pbearne on WordPress.org)