Several WordPress sites on AceHost share one Redis instance for their object and page caches, each in its own namespace. Redis has a memory limit, and when it is reached it evicts the least recently used keys. That works well when most keys are useful. It works badly when much of the memory is holding things nobody will read again.
When I looked, keys on the shared instance had an average remaining lifetime of about six days. Plugins ask for week-long transients as a matter of habit. Some writes asked for no expiry at all, and earlier versions of my drop-in gave those a seven-day backstop. Most of those keys were cold long before they expired.
A Redis key lifetime cap that every write honours
Ace Redis Cache 0.8.13 cut the backstop for keys without an expiry to one day. 0.8.14 went further and added a maximum lifetime that every object cache write honours, including writes that ask for a longer one:
set,add,set_multipleand the shared transient path all store for at most the smaller of the requested lifetime and the cap.- Increments and decrements pull an existing longer lifetime down to the cap.
- The cap defaults to one day. It can be changed per site, or turned off, and a constant in
wp-config.phpwins over the setting. - The generation counter, which the drop-in uses to invalidate a site's keys in one step, is exempt and keeps no expiry.
- The page cache has its own optional cap on fresh time plus stale grace.
A shorter lifetime does not mean more misses for data that is in use. Anything read often is rebuilt and stored again. What changes is that a key nobody reads stops occupying memory after a day instead of a week.
Why not just add memory?
Because the working set does not grow, only the leftovers do. Raising the memory limit gives cold keys more room. Capping their lifetime keeps the memory for the data sites actually re-read.
The value that was never stored
A related trap turned up on a client's site at the same time. Its sitemaps took several seconds each to build and were around a megabyte each, so they were cached as transients. Nothing got faster.
The object cache drop-in has a maximum value size of 64 KB by default, which protects Redis from very large values. Anything bigger is silently not stored. The transient call returns, the next request finds nothing, and the sitemap is built again. A cache that quietly declines a write looks exactly like a cache that works until you time it.
Those sitemaps are now cached as files, written with an atomic rename, and served in around ten milliseconds. Other large fragments on other sites are compressed, and split into chunks where needed, so they fit under the limit.
Redis key lifetime checklist
- Look at the remaining lifetimes of keys on your Redis instance, not only the count.
- Set a maximum lifetime, and make sure explicit expiries respect it.
- Give every key a lifetime, including counters, apart from the few that must persist.
- Find out your object cache's value size limit, and time anything large you cache.