scattering clouds

Coding practice 2: Studying the WordPress Transients API.

So I want to try and modify and maybe even improve upon the flawed “limit WordPress login attempts” code snippet mentioned in my previous blog post.

Since this login snippet makes use of the WordPress Transients API to

  1. keep a count of recent login attempts,
  2. keep track of when the timeout would expire after a system-wide lockdown,

I need to first learn about transients and how it works in WordPress.

WordPress transients are used to store data.

To be more exact, transients are used to store or cache data temporarily by giving it a name and a timeframe. Transients will expire past its given timeframe. Before it expires, you can attempt to retrieve the transient data using its given name.

The idea is to use transients to hold a piece of information temporarily, much like a cache, so that you can quickly access and reuse the information when required.

When that particular piece of information is no longer needed, the transient can be deleted, or be allowed to expire naturally based on its given timeframe.

Transients are a useful concept if you’re building themes or plugins that pull a large amount of data from the database or integrate with outside data sources, especially if your code is going to be running on a high-traffic site. You can use transients to cache particularly slow operations so that site performance stays high when data doesn’t need to be refreshed.

Complete Guide to WordPress Transients, Automattic, Nov 2022

That’s about the gist of it, really.

WordPress’ Transients API is a simplified and standardized method to store data temporarily, so it can be quickly and easily reused at some point in the future.

However, WordPress transients are provided as a nice-to-have method for the sake of speed and convenience. It DOES NOT GUARANTEE that transient data will always be available all the way up to its intended expiration time.

Therefore, the WordPress documentation advises us to have a fall back method to regenerate the cached data IF the transient disappears before it is supposed to.

An example would look a little something like this:

<?php

// Attempt to retrieve the cached data stored in a transient.
$unique_transient_name = 'the_cached_data';
$the_cached_data = get_transient( $unique_transient_name );

if( $the_cached_data === false ) {

	// If the cached data is not available, regenerate the cached data.
	$the_cached_data = fallback_method_to_regenerate_cached_data();

	$one_hour = 60 * 60; // an hour equals 60 x 60 = 3600 seconds.

	// Store or cache the newly generated data for an hour using the same transient.
	set_transient( $unique_transient_name, $the_cached_data, $one_hour ); 

}

?>

Using some syntactic sugar, we can further condense the above example into three succinct lines of code:

<?php

if( false === ($the_cached_data = get_transient('the_cached_data')) ) {
	$the_cached_data = fallback_method_to_regenerate_cached_data();
	set_transient( 'the_cached_data', $the_cached_data, 60*60 ); 
}

?>

Using transients in your plugins and themes is simple and only adds a few extra lines of code, but if used in the right situations (long/expensive database queries or complex processed data) it can save seconds off the load times on your site.

WordPress Common APIs Handbook / Transients

PHP functions for working with transients.

We are concerned with 3 main functions: One for adding / updating transients, one for deleting transients, and one for retrieving transient data.

set_transient()

It is quite trivial to add or update the data and expiration time of an existing transient. All you need is to use the following function:

set_transient( string $name, mixed $data, int $expiration )

$name is a string that is used to set a unique name for the transient. Generally, this string should have no more than 172 characters.

$data is a mixed type containing the data that needs to be cached. It can be an object, an array, a string, or an integer, etc.

Do note that $data SHOULD NOT be used to hold plain boolean values, because it will interfere with validation when you retrieve the $data using get_transient(). Store the boolean value in an array or convert it into integer values instead.

$expiration is an OPTIONAL integer that indicates the amount of seconds the transient will be valid for before expiring. It defaults to the value of 0, which means that the corresponding transient is set to never expire.

set_transient() will return a boolean of true if the transient has been successfully added or updated, and false otherwise.

delete_transient()

Transients will usually expire after $expiration seconds has passed since the last set_transient(), but you can force the transient to expire by deleting it manually. This is useful in cases where you know that its $data has become stale or outdated.

delete_transient( string $name )

$name is a string that indicates the unique name of the transient to be deleted.

delete_transient() will return a boolean of true if the transient has been deleted successfully, and false otherwise.

get_transient()

We can attempt to retrieve the cached $data stored within a non-expired transient by using the following function.

get_transient( string $name )

$name is a string that indicates the unique name of the transient to be retrieved.

get_transient() should return its cached $data value. If the transient does not exist, does not have a value, or has expired, get_transient() will return a boolean of false. This should be validated using the Identical operator get_transient() === false or the Not Identical operator get_transient() !== false.

It should be noted that WordPress will NOT automatically delete expired transients on its own. However, using get_transient() on an expired transient will cause it to get deleted. Non-expiring transients can be deleted by using delete_transient().

Where transients are actually stored within WordPress.

So where does transients store itself under the hood, along with its given $data? The WordPress Common APIs Handbook had this to say:

The Transients API is very similar to the Options API but with the added feature of an expiration time, which simplifies the process of using the wp_options database table to temporarily store cached information.

… Transients are inherently sped up by caching plugins, … . A memcached plugin, for example, would make WordPress store transient values in fast memory instead of in the database. For this reason, transients should be used to store any data that is expected to expire, or which can expire at any time. Transients should also never be assumed to be in the database, since they may not be stored there at all.

WordPress Common APIs Handbook / Transients

This suggests there are two possible places for transients to be stored in:

  • If your website hosting provider has object caching (e.g. Memcached or Redis) enabled on their servers, AND you have the corresponding object caching plugin installed and configured on your WordPress website, transients will be stored in fast memory (better known as RAM).
  • Otherwise, WordPress would default to storing transients in the database. More specifically, transients will be stored alongside your WordPress options in the wp_options database table, OR in the {$wpdb->prefix}_options table if you have configured WordPress to use a custom database prefix.

The drawbacks of WordPress transients.

Expired transients will NOT be deleted automatically.

I thought it sounded crazy too when I first learned about this!

This can a very bad thing for your WordPress website, because unused transients can accumulate and pile up indefinitely in your WordPress database, hogging up precious storage space and dragging down the speed of your site.

To make things even more strange, WordPress actually has a built-in function for deleting expired transients! When researching this topic, I stumbled upon the following Stack Exchange answer by Jesse Nickles, who goes into detail on why things get murky when it comes to deleting transients from the database:

… the issue of transients in WordPress has probably been one of the more confusing subjects (for everyone), and a headache that has reappeared several times over the years.

The idea of “garbage collecting” transients was brought up over 10 years ago … . Despite this, dozens of similar and tangential tickets have been raised since then… one of the biggest problems was that for several years, WP Cron didn’t actually call the function to delete expired transients! Around 5 years ago, this was addressed in a new ticket and a job was added WP Core cron tasks…

Despite all this, the truth remains muddy. Why? Because so many theme and plugin developers don’t understand the proper use of transients, it means that tons of transients in the average WordPress database either don’t have expiration dates, should be “options” instead of “transients”, and many other things that involve failure to communicate and/or implement best practices.

Jesse Nickles, Jul 2022

Jesse goes on to conclude the following:

I think it makes more sense to occasionally delete ALL transients and/or purge your object cache.

… Remember, anything in transients (or object caching) should always be considered temporary non-critical data, so it should always be safe to delete it when needed — otherwise your code is doing_it_wrong.

Which brings us to our next drawback with transients:

The availability of transients is not guaranteed.

As mentioned previously, WordPress does not guarantee that transient data will always be available all the way up to its intended expiration time.

Good ol’ Jesse also offered to provide some clarification on the subject:

If you install an object cache like Memcached or Redis on your server, these key/value entities will no longer be stored as “transients” in your MySQL database, but will instead exist in the object cache space, where they might disappear at any given time because of a server reboot or otherwise.

Jesse Nickles, Jul 2022

So if you need some piece of data to be readily available when you ask for it, you SHOULD NOT store it with a transient, be it non-expiring or otherwise.

The need to clean up transients from a WordPress database.

Since we cannot count on WordPress to clean itself up, and since the login code snippet I plan to modify will probably end up creating a lot of unique transients to keep track of which IP addresses the failed login attempts are originating from, I decided to find out how I can remove transients from the database by myself.

And yes, I do realize there are plenty of free plugins on the WordPress repository that offers to help with just that. But I won’t learn anything if I took the easy route!

Side note: I don’t have object caching enabled on my website just yet, so I will set aside the topic of purging transients from an object cache for now.

For the sake of brevity, I will not go into too much detail on how I experimented with transients. All you get is a description of the steps I have taken:

  1. Install a new WordPress website on a test subdomain.
  2. Install a code snippets plugin on said test WordPress site.
  3. Try out the PHP functions for working with transients using said plugin.
  4. Backup all my websites and databases, then explore the wp_options table within to the database of the test site via phpMyAdmin, which can be accessed through the hosting control panel provided by my website hosting provider.
  5. Devise methods to delete ALL transients en masse from wp_options.
  6. Devise methods to delete a certain subset of transients created by myself.

If you do not know how to do step 1., I suggest reaching out to your website hosting provider for support. Not all website hosting plans will allow you to install another new WordPress website on a subdomain, so your mileage may vary.

For step 2., I use a paid plugin called WPCodeBox [affiliate link], which allows me to write and execute PHP code directly in the WordPress admin dashboard.

However, you can also use the free Code Snippets plugin, which is available on the WordPress repository. Just set your PHP snippet to single-use or “Only run once” through this plugin, and you can get that particular snippet to execute manually in the “All Snippets” list on your plugin dashboard.

You might want to carry out steps 3. and step 4. in tandem, so you can see the effects of set_transient(), delete_transient(), and get_transient() in the database.

Before you start with step 4., brush up on your SQL (just the basics will do), create a full backup of all your websites and databases, and test your backups on staging websites to make sure that it actually works. I cannot stress this enough, because one wrong move can bring down your website(s) for good.

It might be a better choice to install and experiment with WordPress locally on your own computer, but I am reluctant to clog up my only good laptop with random installations.

Also – if you do not have access to phpMyAdmin, you can reach out to your website hosting provider for alternate options to access your WordPress database.

Warning: It is usually a bad idea to mess with a live WordPress database if you are very new to all of this. ONLY experiment with a single database in a TEST environment, and make sure to create backups anyway just to be safe.

How to remove transients from the WordPress database.

According to the WordPress documentation, a single transient can generate up to two database entries:

If object caching is not enabled, WordPress will store transients in the options table wp_options by prefixing its unique $name with ‘_transient_timeout_’ and ‘_transient_’, depending on whether the transient is supposed to expire or not.

Option 1: Run SQL queries directly in the database.

To display a list of all transients in the wp_options table, you can access the database by logging in to phpMyAdmin and running the following SQL query:

SELECT * FROM wp_options WHERE option_name LIKE '%\_transient\_%';

To delete all transients from the same table, we replace SELECT * from the above query with DELETE:

DELETE FROM wp_options WHERE option_name LIKE '%\_transient\_%';

For the purpose of modifying the login snippet, I can add another prefix to the $name of all the transients it creates, e.g. with ‘scttr_clds_attempted_login_’, which can be used as an identifier to differentiate these custom transients from all the rest of the transients found in the database.

Therefore, when the custom transients are stored into the database, their $name will always be prefixed with one of the following:

  • ‘_transient_scttr_clds_attempted_login_’
  • ‘_transient_timeout_scttr_clds_attempted_login_’

Thus I can list all custom transients created by the modified login snippet using:

SELECT * FROM wp_options WHERE option_name LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%';

And delete all custom transients created by the modified login snippet using:

DELETE FROM wp_options WHERE option_name LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%';

Obviously, it is not a good idea to constantly log in to phpMyAdmin just so I can delete transients. What if I typed in the wrong query and deleted something else?

Option 2: Use code snippets to run SQL queries via WordPress.

The solution is to create another PHP code snippet that can be executed (once) manually to clear out all custom transients created by the modified login snippet:

<?php

global $wpdb;

$wpdb->query("
	DELETE
	FROM {$wpdb->options}
	WHERE option_name
	LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%';
");

?>

However, the above code would actually remove ALL custom transients created by the modified snippet, which is still not ideal. We want non-expired transients to stay in effect so we can still keep track of recent login attempts.

The simplest and most straightforward solution would be to create a snippet that calls WordPress’ own built-in function (once) to delete ALL expired transients.

<?php

delete_expired_transients();

?>

Since the modified login snippet will not create any non-expiring transients, the above solution will probably be good enough for the purpose of removing custom transients created by the modified snippet.

But how exactly can one find and remove expired transients from the database?

I studied the code of the built-in function, and created my own snippet to generate a list of all expired transients created by the modified login snippet:

<?php

global $wpdb;
$current_time = time();

$result = $wpdb->get_results("
	SELECT * 
	FROM {$wpdb->options} a, {$wpdb->options} b
	WHERE a.option_name LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%'
	AND a.option_name NOT LIKE '\_transient_timeout\_%'
	AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
	AND b.option_value < {$current_time};
");

foreach( $result as $row ) {
    echo '<p>' . $row->option_name . '</p>';
}

?>

The SQL query in the above code looks a lot more complex because it makes use of SQL aliases and self joins. Either way, now that we have the SELECT query down pat, the DELETE query only requires a slight modification:

<?php

global $wpdb;
$current_time = time();

$result = $wpdb->query("
	DELETE a, b
	FROM {$wpdb->options} a, {$wpdb->options} b
	WHERE a.option_name LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%'
	AND a.option_name NOT LIKE '\_transient_timeout\_%'
	AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
	AND b.option_value < {$current_time};
");

?>

And thus we arrive at a code snippet that can be used to delete expired transients created by the modified login snippet.

Bonus: How to display the output of PHP code snippets.

And as an aside, we can also use the following PHP code snippet to display a list of all transients created by the modified login snippet:

<?php

global $wpdb;

$result = $wpdb->get_results("
	SELECT option_name
	FROM {$wpdb->options}
	WHERE option_name
	LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%';
");

foreach( $result as $row ) {
    echo '<p>' . $row->option_name . '</p>';
}

?>

Unfortunately, you can only view the list produced by the above code if you’re using WPCodeBox. The free Code Snippets plugin is unable to show you its code output at all. In such cases, we can use a workaround by adjusting the above code snippet as follows, and setting it to “Run Snippet Everywhere”:

<?php

function scttr_clds_list_custom_transients(){
	global $wpdb;
	$result = $wpdb->get_results("
		SELECT option_name
		FROM {$wpdb->options}
		WHERE option_name
		LIKE '\_transient\_%scttr\_clds\_attempted\_login\_%';
	");
	foreach($result as $row){
		$output .= '<p>' . $row->option_name . '</p>';
	}
	return $output;
}
add_shortcode('code_snippets_workaround', 'scttr_clds_list_custom_transients');

?>

The above produces a WordPress shortcode [code_snippets_workaround], and you can add this shortcode to any of your WordPress posts or pages to view its output. Of course, you probably don’t have any transients in your database that matches the above query, so the shortcode will not display anything.

Instead, you can change the query as follows to get the shortcode to display the names of the first 10 transients from your wp_options table:

<?php

function scttr_clds_list_some_transients(){
	global $wpdb;
	$result = $wpdb->get_results("
		SELECT option_name
		FROM {$wpdb->options}
		WHERE option_name
		LIKE '%\_transient\_%'
		LIMIT 10;
	");
	foreach($result as $row){
		$output .= '<p>' . $row->option_name . '</p>';
	}
	return $output;
}
add_shortcode('code_snippets_workaround', 'scttr_clds_list_some_transients');

?>

Closing words.

PHEW! We studied the WordPress Transients API, learned how to get, set, and delete transients, brushed up on SQL, and learned how to create transients with identifiable names such that it can later be removed from the database if need be.

All that just so I can try to improve upon the flawed code snippet discussed in my previous blog post. It is probably a lot easier to just install a few more plugins and call it a day, but hey, I’m trying to teach myself some WordPress over here.

Is it worth the effort? Who the hell knows… At least I kind of enjoyed the process.

And maybe that is all that matters in the end.

back to top