7

First off, I'm trying to ensure due diligence and not duplicate questions that have come up before. There are numerous posts on stackexchange, and various other places, that discuss views caching and even some specifics about the different types of view caches (e.g., here and here). However, I've yet to find any details that sufficiently explain the relationships between different layers of views caching, and how they may impact case-specific caching decisions. For simplicity I'm also focusing the scope of this question to out-of-the box views 7.x-3.x caching options independent of other layers offered in the contrib space.

Views exposes 2 layers of time-based caching for each display, a "query results" cache and a "rendered output" cache. The specifics of what raw data each cache encompasses is clear, but the ways they interact is not. I'm specifically wondering if a hit on the rendered cache will completely bypass they query cache, or if the 2 layers operate separately. I've seen some references that claim the former (like this one), and some that claim the latter (like this one).

Theory 1 - Separate cache layers

I was initially under the impression (and hoping) that each layer is invoked separately "in series", and that the rendered output cache hashes the actual query results within its cids. This way I would have an elegant way to cache the most expensive post-query load/build work, while still ensuring that new content is immediately reflected in the view ("query results" cache off and "rendered output" cache on). Some quick tests reveal that this might not be possible though, as the rendered output cache may not be aware of the current query results.

Theory 2 - Rendered output cache "encompasses" query results cache

The other possibility is that the rendered results cache is keyed by a hash of the query itself (not the results). If this were the case, a hit on it could return the rendered output directly, without the need to consult the query cache, or DB. If this were the case then I suppose it would not make much sense to have the "query results" cache set with a time interval less-than the "rendered results" cache. The advantage of course would be that one could refresh the rendered output more frequently (if they have lots of dynamic theme logic, or frequent entity updates), while still avoiding direct queries to the DB. However, for all but the most complex view queries, this kind of separation does not seem all that advantageous.

I've done some "black box" tests that point to theory 2, but I'm not sure if there are some other settings that might be at play or if the answer differs by views version. I've also explored the code a bit, but I find it frustrating that most views' plugin methods are undocumented and occasionally hard to follow. No matter what, I think the answer to this would be useful to have as a reference for others.

3
  • From experience I'm pretty sure it's theory 2. Will try to find some time to check later (if someone hasn't already cleared it up), this would indeed be a very useful reference
    – Clive
    Feb 12, 2015 at 18:13
  • In the one post it is stated: Views render cache uses the result set of a query as its cache key. When I only cache the rendered output (1 hour), and add new content that content is not displayed. Which suggests that the result set is not the cache key. Feb 12, 2015 at 19:00
  • Yeah, both the comments above point away from the theory that each layer can be leveraged separately. I've just been hesitant to jump to that conclusion given the conflicting notes I found floating around on the topic. I'm really wondering if views < 7.x-3.x behaved differently, thus propagating confusion. Even if this is truly the case, any comments about when it's well-justified to have differing settings for each layer would still prove very useful.
    – rjacobs
    Feb 12, 2015 at 19:26

1 Answer 1

4

I looked into some of the internal views logic, and from what I can there are elements from each theory that are correct, but neither is 100% accurate. It seems that both caching layers are separate, and always checked in series, so the "rendered output" cache does not really have the ability to "bypass" the query layer. Additionally, the query results do not factor-in to any cache keys, so changes to query results unfortunately do not trigger a refresh in the rendered output.


7.x-3.x

The "query results" cache is managed in view::execute() and the "rendered output" cache is managed in view::render(). Because execute() is called before the "rendered output" cache logic, a "rendered output" cache hit cannot bypass the query layer. It's not totally clear to me why this is as seems a little wasteful to run a query, or even check a query cache, if the display will just be built in-full from a cache. I suspect that there is plenty of other logic in view::execute() (access checks, hooks, plugin management) that must always be run independent of caching, and the "query results" cache part could not easily be separated from it. Anyway, that detail is a bit tangent to the question, but maybe someone else would be able to comment on it.

In terms of cache keys both cache layers are keyed by the view name, the display name, the cache type and then a hash of a bunch of contextual "stuff" from views_plugin_cache::get_cache_key(). A look at this get_cache_key() method is insightful as it shows that a some common contextual data is always factored-in (user roles, language, base_url), but the "rendered output" case takes things a bit further by also hashing the raw query string, but not the query results. So if the raw query changes, the rendered output will be refreshed, but if the query results change, the rendered output will still be returned from cache.

So I think that from a performance perspective each cache layer can really be dealt-with independently, but the "rendered output" cache will still most directly govern how often "stale" data is displayed. I could further conclude that:

  • The refresh interval for the base list of items in the view is greater-than-or-equal-to whichever cache interval is larger (the actual list of items can't update until both caches expire for any given view).
  • If the "rendered output" cache for a given view refreshes before the "query results" cache, the list of items won't refresh, but the content displayed for each item could (i.e. a new item added to the list 5 seconds ago still won't be shown, but teaser text that was updated 5 seconds ago on an item that was in the list previously would update).
  • Even when the "query results" cache is not influencing the final output it still offers a performance boost for view::execute().

It's also worth noting that some of this behavior could be changed by overwriting methods in the views_plugin_cache class locally, such as views_plugin_cache::get_cache_key().

8.0.0

As of 8.0.0-beta7 views (core) retains the option for time-based caching. As best I can tell it will behave like 7.x-3.x but it's really too early to do any conclusive testing - see this meta discussion. What is clear however is that many of the concerns and limitations around views time-based caching may become moot, as it looks like D8 views will also support tag-based caching. This should offer a way for truly smart expiration of cached data.

Not the answer you're looking for? Browse other questions tagged or ask your own question.