Problem/Motivation

Views rows are render cached individually, but this is quite inefficient. Unlike entity view mode caching, the rows are specific to an individual view configuration, and we already render cache the entire view output at a higher level. It's possible that the view is invalidated via list cache tags and most of the individual rows aren't, but in practice the hit rate is still going to be pretty low.

When there's a miss on the view render cache but a hit on the rows, other caches that make the row rendering faster are likely to be warm too, like entity caches. On a cold cache request, it's a lot of cache sets, especially once we take into account cache redirects.

This is what the cache items look like:

     60 => "views:fields:recipe_collections:block:fdb27fba4d5f968ea52227f521bd1414d24eab3c70a5cf7043a52b3309c26fc8:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      61 => "views:fields:recipe_collections:block:71f37900c45567604b647479931173987ea1eba0cb060878670a8fa628126799:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      62 => "views:fields:recipe_collections:block:71f37900c45567604b647479931173987ea1eba0cb060878670a8fa628126799:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      63 => "views:fields:recipe_collections:block:c96be8074bfbccf8cc28c8759bdd19b3cc9c82438ebaaeb686f0d5cf6646ca13:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      64 => "views:fields:recipe_collections:block:c96be8074bfbccf8cc28c8759bdd19b3cc9c82438ebaaeb686f0d5cf6646ca13:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      65 => "views:fields:recipe_collections:block:ed61bf5340f38fdaa5e9446dae63599474d5f91c7f742be8db07d9a2a4e8191f:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      66 => "views:fields:recipe_collections:block:ed61bf5340f38fdaa5e9446dae63599474d5f91c7f742be8db07d9a2a4e8191f:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      67 => "views:fields:recipe_collections:block:f02695c8a7ff3b3eda5904ff52b6013770aef87aed7801be1ed80fb6c132ab20:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      68 => "views:fields:recipe_collections:block:f02695c8a7ff3b3eda5904ff52b6013770aef87aed7801be1ed80fb6c132ab20:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      69 => "views:fields:recipe_collections:block:38c57f665e9959898504933e9a41749afa38611fd9594cb24d58cb789ecb466c:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      70 => "views:fields:recipe_collections:block:38c57f665e9959898504933e9a41749afa38611fd9594cb24d58cb789ecb466c:[languages:language_interface]=en:[languages:language_url]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"
      71 => "views:fields:recipe_collections:block:09a8013364aaba62b9f2652288456e1c393151a0f3be323daa5730e9b3accc75:[languages:language_interface]=en:[theme]=umami:[user.permissions]=80dc35c311ab586f38e4b3f5e9ddc4e5ae9d290d51308881d6855312548b4cc5"

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3564937

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catch created an issue. See original summary.

catch’s picture

Status: Active » Needs work

This will break views caching tests.

Also if we go ahead here we can probably deprecate CachePluginBase::getRowCacheTags()

catch’s picture

Issue summary: View changes
catch’s picture

Did some profiling:

Umami front page logged in as user/1

With and without the MR, I warmed the caches, then deleted only the views block render cache entry for the recipe collections view, which uses fields:

DELETE FROM cache_render WHERE cid LIKE 'entity_view:block:umami_views_block__recipe_collections_block%';

Then hit the page again and profiled it. This isolates the page differences to the row caching change as much as possible.

On my local while profiling, the row caching is saving around 30ms out of 60ms from that method (see screenshots), so it's having an effect, but it's not much - e.g. it probably takes as much as that to save the cache items on cache misses.

catch’s picture

Status: Needs work » Needs review

Background reading:

#1867518: Leverage entityDisplay to provide fast rendering for fields
#2381277: Make Views use render caching and remove Views' own "output caching"
#2450897: Cache Field views row output

What I think happened is that #1867518: Leverage entityDisplay to provide fast rendering for fields mostly fixed the performance issue that #2450897: Cache Field views row output was also solving, but they were also interdependent with each other and we didn't fully re-evaluate the need for the caching issue once the field rendering issue landed.

The issue summary from the original issue still has:

how much extra work on a cold cache (i.e. a view with 50 items per page will be 50 additional cache sets)
- can we use multiple get on the rows to avoid 49 individual cache gets? Are 49 individual cache gets + 1 cache set worse than just doing the rendering?

And we never actually resolved those.

I think we possibly could manage to add in 'render cache multiple get' here now since we added that for placeholders, but that doesn't address the extra overhead on cache misses and the overall cache storage needed for the extra items, so can just as well remove a caching layer.

smustgrave’s picture

I will not be able to mark this as cache is my worst but any concern with removing keys from #cache? Could anyone of been using that?

catch’s picture

@smustgrave #cache['keys'] is what determines whether a render array gets cached in its own right in the render cache or not, it's only used internally by the renderer, and in this specific case I'm not sure that render array even shows up somewhere you'd be able to alter it.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Been 2 weeks and don't want to leave this one hanging, so I'll go on a limb.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Reviewed & tested by the community

Rebased.

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Reviewed & tested by the community

Rebased.

catch’s picture

Rebased.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

I think we need to consider views which are not using entity displays here a bit more carefully for example what is the impact on the admin content view? Maybe we should make the row cache dependent on view style? If it is using entity display's then this shouldnt be used but if it is using fields then it should be?

catch’s picture

I warmed caches on admin/content with Umami, then clicked on the first item in the list, edited and saved it, then checked xhprof for the next admin/content load.

That should be more or less the worst case for this change since all but one row will still be in cache.

main views row rendering comes in at around 360ms, the MR here comes in at around 780ms - so rendering all but one items on admin/content the cache saves about 400ms.

However, on a cold cache (after visiting admin/help first), this is saving over 100,000 function calls and approx 300ms (with xhprof overhead etc.) The difference on my local was 2.27 seconds vs 2.0 seconds for the entire request. Can't look at only row rendering in this case, because the overhead is in the render system itself with render cache gets/sets to have to compare total function calls/time really.

Once we get into 10% of 20% of the rows not being in cache (e.g. if other site editors are adding/editing content on the site in between the page getting viewed), then the extra cache sets for those rows will probably cancel out the cache hits for the rows that haven't changed.

So for me it's a worthwhile trade-off to make the worst case better, keeping the best case unchanged, and then the middle cases will be somewhere between a fair bit worse, neutral, and a bit better (e.g. a handful of rows in cache but most rows not). Apart from the raw performance, we're also able to save space in the cache_render bin.

catch’s picture

Another thing here is that a lot of requests to admin/content or admin/people will immediately filter the results - when the results are filtered the likelihood of a cache hit on the rows in the filtered list is very low indeed. So with the cache even if we save time on the first request to admin/content, the second request to admin content with filters can be slower.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new2.42 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review
catch’s picture

Had a quick look at what actually takes the time rendering the views rows, and a lot of it is individually looking up path aliases which would be solved by #3518668: Use Fibers for rendering views rows, and a lot of the other time is in access/permissions checks which should be improved by #3539161: Static cache access policy checking.

Very hard to separate out exactly how much is contributed by these and in turn how much would be saved especially since this and the other views issue conflicts, but definitely identifiable savings to be made without additional persistent caching.

alexpott’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

The performance tests have changes again.

@catch thanks for the analysis on #17. I think given the plans to improve views performance in other ways going ahead here feels like a good idea.

catch’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs reroll

Rebased.

alexpott’s picture

Version: main » 11.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 3245d571a9f to main and 27d5b0662e3 to 11.x. Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • alexpott committed 27d5b066 on 11.x
    perf: #3564937 Remove duplicative caching of views rows
    
    By: catch
    (...

  • alexpott committed 3245d571 on main
    perf: #3564937 Remove duplicative caching of views rows
    
    By: catch
    

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

quietone’s picture

Updated CR and published.