This page’s content is still “work in progress” – please bear that in mind whilst reading
Use of Transients is not without its issues and general bear-traps, all of which you should be aware of.
Transient Name Length
As Transients are inserted into the Options table, and the option name field is a fixed length, this means that Transient names are a fixed length too. But this is not validated by the WordPress Transient functions.
Let’s use an example.
The name field is 191 characters long and we create a Transient with a name of that length. What we’ve forgotten is that
_transient_timeout_ (or even
_site_transient_timeout_) is prefixed to the name. If our Transient has an expiry, and let’s say in this case it does, that means we have 172 characters for the name, or 167 if it’s a site Transient.
Does WordPress reject it or even truncate it? No, it goes ahead anyway. Thankfully MySQL truncates it. But without any error from WordPress, the developer may be unaware. So, what happens when you then try and read this back? Well, the full length Transient name will be looked for in the database and won’t be found, as it no longer matches the now truncated Transient that we stored before.
What makes this worse, of course, is that you could exceed the length for the field that contains the expiry information but not the main field which holds the transient data – do this and your Transient will now no longer expire.
I originally raised the issue with Transient name length with the Core team a few years ago. At the time the
wp_options field length was just 64 characters, so it was an even bigger issue. I suggested 1 of 4 things should be done…
- Return an error if the name is too long.
- If trying to fetch a Transient and the name is too long, truncate the name to 64 characters before looking it up – that way a previously truncated Transient can still be found.
- The documentation should be updated to make mention of this limit.
- A combination of all of these things.
In the end, after much discussion, number 3 was done and the documentation updated. Oh, and the field length was increased from 64 to 191 characters.
But WordPress still doesn’t check for any of this, hence “will silently fail”, if the limit is exceeded.
Everyone seems to misunderstand how transient expiration works, so the long and short of it is: transient expiration times are a maximum time. There is no minimum age. Transients might disappear one second after you set them, or 24 hours, but they will never be around after the expiration time.WordPress Codex
Considering this is from the Transients API section of the codex, it’s actually wrong (kind of) and I’ve highlighted the bit that is.
The thing is, expired Transients, when stored on the database, are actually only removed when you try and fetch them – there is no automated housekeeping process.
Why is this a problem? Consider how you may want to use transients for the purpose of caching. Let’s say you want to generate some content in a post, based on a number of parameters passed, and you want to use Transient to cache that content. When the parameters change you want to refresh the cache. The easiest way to achieve this is to create a Transient name based upon a hash of all the parameters – that way the same content, with the same parameters, will use the same cache but also, any change to the parameters, will force a new Transient to be created.
Now, if housekeeping ‘worked’ in this instance, all would be good.
Step forward my own plugin, YouTube Embed. This generates YouTube code based upon the parameters you pass and works exactly as per my example. It wasn’t until this was reported that I realised what was happening…
YouTube Embed [is] filling up my wp_options database with 3.8GB of logsYouTube Embed Support Forum
Once a parameter changes, a new Transient is generated and I never read the old one again – even though it expires, it never gets deleted.
Once again, this was raised with Core. But there was an issue. Some plugins, including some quite popular ones, use Transients to store permanent data. This is bad but it meant that Core did not want to make any changes that would knowingly break some people’s sites. As stated at the time…
This leaves much to be desired, but we don’t want a core update to be blamed for breaking a site that incorrectly assumes transients aren’t transient.Trac ticket
In the end I created my own plugin, Transient Cleaner, which uses cleaning code created by myself and honed by the Core team but eventually not used in Core itself. It’s popular, gets great reviews and, importantly, has never broken a site (that has been reported to me, anyway). Run from a daily cron, this simply removes any transients that have expired. As for my YouTube Embed plugin, I simply implemented a scaled down version of Transient Cleaner in that, so it cleans up as it goes along.
Now, if your code always reads back the Transients then this shouldn’t affect you, should it? So, what happens if a user, for example, uninstalls your plugin? Those Transients will not get read and will remain on the table forever. So, this is, potentially, an issue that can effect anyone writing code with Transients.