[bs_button size=”md” type=”default” value=”Back to ‘WordPress Transients API'” href=”https://artiss.blog/transients/”]
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 was neither documented nor validated by the WordPress Transient functions.
Let’s use an example.
Let’s say the name field is 64 characters long and we create a Transient with a name of that length. What we’ve forgotten is that
_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 only have 45 characters for the name. And because there’s no validation of the length our, now, 83 character name isn’t going to fit. 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 83 character 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. 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…
[Transient names] should be 172 characters or less in length as WordPress will prefix your name with “_transient_” or “_transient_timeout_” in the options table (depending on whether it expires or not). Longer key names will silently fail.WordPress Codex
So, the field length in the option table for the name is now 191 characters. As mentioned before, the name you supply for the transient is prefixed, for non-networked sites, with
_transient_timeout_, the longer of which is 19 characters. This means that the longest transient name is now 172 characters, or 180 if you don’t specify an expiry, hence the 172 limit mentioned above (the lesser of the two)
But WordPress still doesn’t check for any of this, hence “will silently fail”, if the limit is exceeded.
Now, if you’re using a networked site and you save site-wide transients, then this is a different headache as Core didn’t change their field size, which remains at 64 characters. As these are prefixed with
..the transient name should be 40 characters or less in lengthWordPress Codex
Again, no validation takes place.
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 and I’ve highlighted the bit in particular 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 users reported having 1.5GB Options tables filled with Transients from my plugin that I realised what was happening. 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.
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.