Blocked on #2433591: Views using pagers should specify a cache context
Used to be postponed on
#2450897: Cache Field views row output
and
#2452317: Let views result cache use cache contexts
Problem/Motivation
- We want to be able to use render caching for Views, because Views' output caching still requires Views to be initialized and hence is still very costly. See the #2323335: [meta] Improve Views' caching issue summary for details.
- Security! Views' results & output caching right now allows information disclosure because it doesn't take the necessary cache contexts into account. See comment #10.
Proposed resolution
Introduce render caching at view display level (Panoramic Views™). Each cache plugin will be responsible for determining the actual display render cache behavior, which will replace output caching. Result caching will remain the same.
- The default cache plugin will become the Tag-based one, which will just implement render caching the usual way, at display level.
- For very complex (uncacheable) displays it will be possible possible to disable render caching via the None plugin.
- Finally, we will retain the Time-based plugin, which will implement regular render caching at display-level, but will override the invalidation strategy by ignoring invalid cache tags, so that only the cache entry expiration timestamp will matter. The actual expiration time will be configured via the usual settings. We will need a new
#cache_min_age
to avoid bubbling it to the upper levels (see #38).
The display renderable array will be provided by a static method on the DisplayPluginBase
class, to avoid loading the view altogether. In case of a cache miss the renderable array will be available to the actual display object, that will be able to perform any required modification.
Remaining tasks
- #2433591: Views using pagers should specify a cache context
- #2263569: Bypass form caching by default for forms using #ajax.
- #2450897: Cache Field views row output
- #2487099: Set cache contexts for exposed sorts / items_per_page / offset.
- #2489966: The Views table style plugin does not specify cache contexts for click sorting
- #2498857: Support min-age in render caching
Tasks in that issue:
None
User interface changes
None.
API changes
Code which is not already render cached, like page controllers, should now use for example Page::buildRenderable($view_id, $display_id, $args);
in order to be rendered. This returns a minimum render array, that is render cached, and by that potentially much faster.
Review help
+++ b/core/modules/comment/comment.module
@@ -211,6 +211,29 @@ function comment_node_links_alter(array &$node_links, NodeInterface $node, array
+function comment_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
Code to build up $entity->rss_elements is moved from the CommentLinksBuilder to hook_entity_view(). The reason for that is that we now don't do early rendering (drupal_render() inside each individual row) anymore. It used to work like this: Early rendering => triggers #post_render_cache => builds up the links and the $entity object is changed.
In rendering the row, views-view-row-rss.twig.html, we took over the changed $entity object. Now the rendering happens, when the row template is rendered, which means, that
$elements is not setup yet.
Changes to the way how views contextual links are rendered
For views contextual links we want to add the contextual links to the $page render array, in order to have the UX we want. As long we did not render cached those entries, it used a pre_render callback for #type 'page',
which called out to views_get_page_view(). This function cannot longer work, because the view is not always executed anymore. Instead, we introduce Views:: getPageRenderArray()
, which
is called from the page controller itself. This allows to retrieve just the metadata we need, like the view ID, display ID, and whether we want to show contextual links.
Why do we override cache keys in ViewExecutable::setItemsPerPage
There are two ways to render a view. a) You start with a simple render array, see API changes. b) You have a view executable already
For the simple render array, which is the potential much better cacheable one, we still want to vary for example the current page. In this case, we just have one context available, the global one. Therefore
we use cache contexts, which define global variations of the content.
In case you already have a view executable available, you control the context, so the context is not global anymore. In this case the variation can be added to the cache keys, so for example we set 'page:$number' into cache keys.
Comment | File | Size | Author |
---|---|---|---|
#240 | 238-240-interdiff.txt | 3.42 KB | alexpott |
#240 | 2381277.240.patch | 148.96 KB | alexpott |
#238 | interdiff.txt | 4.19 KB | dawehner |
#238 | 2381277-238.patch | 148.3 KB | dawehner |
#236 | 2381277-235.patch | 147.42 KB | dawehner |
Comments
Comment #1
dawehnerIs there any chance that we can try to make that an opt-in feature?
As long we haven't figured out cache tags yet, we don't have any invalidation yet.
Comment #2
Wim LeersHrm, that's right. Let's not make it complex by making it an opt-in feature. Let's instead block this on #2381217: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache (which in turn is blocked on #2372855: Add content & config entity dependencies to views and #2342651: Cache tags for *all* config entities).
Comment #3
Wim Leers#2372855: Add content & config entity dependencies to views landed, this is now only blocked on #2342651: Cache tags for *all* config entities and #2381217: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache.
Comment #4
Wim LeersComment #5
Wim LeersAs per #2381217-5: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache, that issue is not actually blocked on #2342651: Cache tags for *all* config entities, so it can continue, and hence this issue in turn is only blocked on 1, not 2 issues.
Comment #6
Wim Leers#2318377: Determine whether a view is cacheable and its required contexts, store this i/t config entity did the hard work of adding the necessary methods/APIs/metadata to Views handlers & plugins to be able to gather the necessary cache contexts without having to initialize all handlers & plugins. That's great.
But it didn't add test coverage. Nor did it actually put the added APIs to use. That will be done in this issue.
This issue will roughly have replace the logic in
CachePluginBase::generateResultsKey
with the cache metadata that is generated by that issue.Comment #7
Wim Leers#2381217: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache has landed, #2183039: Views' cache tag-based caching only sets cache tags for related entities based on Views relationships, should also do so for entity reference fields turns out to be unnecessary anymore, so now this is effectively unblocked.
Comment #8
Wim LeersBut now this is actually blocked on #2445743: Allow views base tables and entity types to define additional cache contexts.
Comment #9
Wim Leers#2445743: Allow views base tables and entity types to define additional cache contexts landed, now fully unblocked!
Comment #10
Wim LeersIMO this is actually even a security issue, for the reasons I explained in #2323335-6: [meta] Improve Views' caching. I think this may need to be critical because of that.
Comment #11
Wim LeersNote that #10 does not point to a security issue in D7.
First of all: #10 only describes an information disclosure bug about the *rendered* results. The results themselves (i.e. "which things to show") is secure in D7 and D8, because that happens at the query level.
So this is only about the rendered rows.
In Drupal 7, we didn't have cache contexts. It was/is the Dynamic Content Wild West. In Drupal 7, if content listed by Views is very dynamic and potentially sensitive, it is your responsibility as the site builder/developer to subclass the Views cache plugin and write your own, and as such hardcoding your own "(cache) contexts" in addition to the default ones.
To make this very concrete: in Drupal 7, there's no entity render caching, but in Drupal 8 there is. If an entity is varied by a cache context not captured by Views' default cache plugin (see #10), then Views' output cache doesn't care, and will serve the exact same node X to users A and B (both of whom legitimately can view node X, thanks to the query-level access checking), even though user A can see a field Y that user B shouldn't be able to see. The reason user B can see it too, is because it's varied by a
foobar
cache context that Views doesn't care about.Comment #12
effulgentsia CreditAttribution: effulgentsia commentedRaising to critical, because while in Drupal 7, it's the site builder's responsibility to think about cache poisoning issues when enabling block or Views caches, that is no longer the case in Drupal 8, except for Views output cache, which I think qualifies this as a real D8 security bug. Tagging for triage though, since the branch maintainers might not see it that way.
Comment #13
catchfwiw I agree with the reasoning on this.
Comment #14
dawehnerGood point @effulgentsia
In general I think we should clearly split up work on the result cache from other kind of cache, see #2452317: Let views result cache use cache contexts
Comment #15
Wim LeersYes, let's improve the results cache first!
Comment #16
effulgentsia CreditAttribution: effulgentsia commentedDiscussed with webchick, alexpott, and xjm, and they also agreed with #12 / #13.
Comment #17
plachHow does this relate to #2450897: Cache Field views row output?
Comment #18
effulgentsia CreditAttribution: effulgentsia commentedAt one level, unrelated. This issue is about caching the entire rendered View (or rather, doing it in a more D8-proper way rather than HEAD's legacy custom approach). While #2450897: Cache Field views row output is about a single row.
At another level, I don't know, but suspect that #2450897: Cache Field views row output will be easier to do once this issue is finished. If that's true, I recommend postponing that issue on this one.
Comment #19
plachSounds reasonable to me, the D7 version of views_row_cache is a mighty hack due to the fact that we never get to have full render arrays...
Comment #20
effulgentsia CreditAttribution: effulgentsia commentedDiscussed this with Wim today, and he suggested the opposite of what I said in #18. Specifically, that #2450897: Cache Field views row output can proceed without this issue, because we already have full render arrays where we need to for that issue, and that per #15, this issue is more blocked on #2452317: Let views result cache use cache contexts than on anything rendering related. In fact, Wim suggested that doing #2450897: Cache Field views row output and #2452317: Let views result cache use cache contexts first, independently of each other, should then make this issue simple, since caching a rendered View is just the combination of caching the results plus the rendering of each result. So, postponing this on those two.
Comment #21
YesCT CreditAttribution: YesCT commentedputting that info in the summary, and postponing.
Comment #22
Wim Leers#2452317: Let views result cache use cache contexts landed.
Comment #23
Wim Leers#2450897: Cache Field views row output is very close!
Also: the related-to-this-but-not-blocking-this #1867518: Leverage entityDisplay to provide fast rendering for fields landed last week.
Comment #24
dawehnerWell, either we bubble up cache metadata via cached or uncached rows, I don't see why this should be blocked on the other issue, at least the basic building blocks can be achieved entirely independent.
Comment #25
Wim LeersPer #24.
Comment #26
dawehnerJust curious what happens :)
Comment #28
Wim Leers:)
Comment #29
dawehnerWe identified which issues are causing these failures:
Comment #32
plach#2450897: Cache Field views row output was committed (yay!), updated the test failures count by sending #26 to the bot again.
Comment #33
dawehnerUpdating the issue summary
Comment #36
plachDiscussed with Daniel what role cache plugins should have with respect to render caching at display level and we came up with the Panoramic Views Render Caching strategy. Each cache plugin will be responsible for determining the actual display render cache behavior, while result caching will remain the same:
rendered
one), so that only the cache entry expiration timestamp will matter. The actual expiration time will be configured via the usual settings.The display renderable array will be provided by a static method on the
DisplayPluginBase
class, to avoid loading the view altogether. In case of a cache miss the renderable array will be available to the actual display object, that will be able to perform any required modification.Comment #37
plachAnother one: #2489966: The Views table style plugin does not specify cache contexts for click sorting.
Comment #38
Fabianx CreditAttribution: Fabianx for Acquia commentedDiscussed with plach at the airport:
For 3. we will need a #cache_minage property (that does not bubble and hence is not within #cache), which controls that the RenderCache service should set that special flag for retrieving the cache, so that invalid / expired items are returned.
Will likely need some more thought as we do not want to call the cache plugin at all, but determine on cache retrieval, so we probably should be setting that always and then check if that property is present in the retrieved render array.ù
If it is set and invalid, we compare the timestamp of the minage with the actual age timestamp, else we return a cache miss.
--
- None plugin should just set max-age 0 and we are done.
- Tag based output kinda does nothing on itself. (what we already do in core except that we remove the old and now due to bubbling broken output cache support)
Comment #39
plachHere are a couple of test fixes.
Comment #41
plachOnce again too many branches ;)
Comment #42
dawehnerSo current work.
Comment #43
dawehnerMore work.
Comment #45
dawehnerSome more fixes.
Comment #47
dawehnerA couple of test fixes
Comment #49
dawehnerFixed quite some of the failure:
Comment #51
plachI investigated test failures a bit:
MiniPagerTest
,FieldWebTest
,FrontPageTest
should be fixed by #2433591: Views using pagers should specify a cache context and #2489966: The Views table style plugin does not specify cache contexts for click sorting.ContextualDynamicContextTest
is caused by contextual links not providing cacheability metadata.BulkFormTest
(and possiblyExposedFormTest
) seems caused by cached form tokens / build ids, we need post render cache placeholders for them.Comment #52
dawehnerApplied #2489966: The Views table style plugin does not specify cache contexts for click sorting and #2433591: Views using pagers should specify a cache context and fixed some small points.
Agreed these two subissues would solve a lot of the problems.
Comment #53
dawehnerJust trying to expand the test coverage and well, the page cache does not take into account the max age bubbling.
Comment #55
catchCommitted one of the blockers and reviewed and bumped status of the other one.
Patch looks really encouraging to me - great that the necessary changes really aren't that much after all the other issues.
Comment #56
YesCT CreditAttribution: YesCT commentednoting the blocker in the summary.
Comment #57
Fabianx CreditAttribution: Fabianx for Acquia commentedPostponed on #2433591: Views using pagers should specify a cache context to keep critical stats properly.
Comment #58
dawehnerNot really sure why it should be postponed. Its required to make it pass, but its not required to continue on this issue.
Comment #60
dawehnerDid some debugging on the failure in
Drupal\contextual\Tests\ContextualDynamicContextTest
The problem is primarily caused by the code in
views_page_display_pre_render
::In case we have a cache hit,
views_get_page_view()
does not exist ... as we have no active view. This is a general problem, which will also hit themers, given that they want to change / add some css classes depending for example on the current result of the view.At least for this case its enough to check for the route object itself.
Comment #61
Fabianx CreditAttribution: Fabianx for Acquia commentedWhy is the views_page_display_pre_render() called when there is no view?
Can we call it in the #pre_render callback itself and use another hook for being able to tamper with the render array itself (i.e. cache keys, display parameters, cache contexts up-front to optimize experience)?
Comment #63
dawehner... We want to place the contextual link not on the level of the rendering of the view, but on the page level, so we add that as pre_render function to #type page.
Comment #64
Fabianx CreditAttribution: Fabianx for Acquia commentedAs this is page specific, could we instead add the necessary information to $element when we have constructed the view?
Then we would have all overhead in #pre_render, but the contextual link is still dynamically created ...
Comment #65
plachUpdated issue summary with #36 and #38.
Comment #66
Wim LeersCache::mergeContexts()
The documentation for this method is missing.
Comment #67
dawehnerDoes that allow you to bubble up the contextual links information? We want to place it on the page render array.
Comment #68
Fabianx CreditAttribution: Fabianx for Acquia commentedNo what I mean is to add information so that we can re-create information without having the view object itself:
Lets take a look at the _dependencies_ on $view of the https://api.drupal.org/api/drupal/core%21modules%21views%21views.module/... function:
So overall this depends on:
- The location, which we have already anyway.
- The display ID - which we have before #pre_render stage
- The plugin ID - which we have before #pre_render stage
- The view storage ID - which we have before #pre_render stage
- The getShowAdminLinks(), which we need to still analyze:
https://api.drupal.org/api/drupal/core!modules!views!src!ViewExecutable....
only depends on the display option, which we can cache as an extra property.
Overall:
location - have already -- cheap
display ID - have already, part of cache keys -- cheap
plugin ID, view storage ID, getShowAdminLinksResult() can put during #pre_render into the $element like:
#view_plugin_id
#view_storage_id
#view_display_show_admin_links
and then use #cache_properties to store this across cache get / sets and boom we can simplify views_add_contextual_links() to no longer rely on the $view object itself, but just on properties in $element - regardless if it was retrieved via #pre_render or cache get.
Comment #69
Fabianx CreditAttribution: Fabianx for Acquia commentedOh and the thing I missed:
No, bubbling is not possible, but as this relies on a global, I would not feel bad to use views_set_page_render_array() / views_get_page_render_array() instead to keep BC.
As its part of the main controller, it would be executed always anyway ... (unless we cache the whole page)
Comment #70
Fabianx CreditAttribution: Fabianx for Acquia commentedAnother possibility is (once placeholders exist) to just use a placeholder in #attached placeholders, but then use the placeholder string in the page pre_render. [ and just share the placeholder string between them as e.g. a CONSTANT ]
That is likely the absolute cleanest solution, but can only be done as follow-up as placeholders should obviously not block this ...
Comment #71
Wim Leers#2433591: Views using pagers should specify a cache context landed.
I think we should also remove the "output caching" concept from
CachePluginBase
here. That makes it so thatCachePluginBase
would only need to care about results caching, which is an important conceptual/maintainability simplification.OTOH, that may make it harder to implement the strategy in #36? Not sure.
Comment #72
Fabianx CreditAttribution: Fabianx for Acquia commented#71: No, that _is_ the strategy of #36 actually (as explained to me by plach), so we are all on the same page.
Comment #73
Wim LeersBut it's not yet in the patch, which is why I wasn't sure about that. Excellent, then :)
Comment #74
dawehnerI'm not entirely convinced that #68 makes things easier than the current state of the patch, anyway, here is a patch for just that.
Comment #75
dawehnerBut for sure I totally get the point in not having to load the view object on cached pages.
Comment #77
Fabianx CreditAttribution: Fabianx for Acquia commentedOverall nice, some nit-picks on how #68 was implemented.
The idea was that $element would have that already cached, just need to find the right element on the page.
Needs to use '#view_id', '#view_display_show_admin_links', etc.
And this is likely not the right place for setting the #cache_properties value.
Comment #78
dawehnerWell, the question is whether we can assume that the main content is placed in a particular level on the page render array, I guess we can?
It might be easily the case that maybe that regions are rendered before the $page array is assembled together
Comment #79
Fabianx CreditAttribution: Fabianx for Acquia commentedThat is why I voted for simplicity with adding _views_get_page_render_array / _views_set_page_render_array (@internal).
Comment #80
dawehnerFirst trying to make blocks lazy, I guess we actually don't need that because block caching already does all we need?
Comment #81
Wim LeersCorrect. In
\Drupal\block\BlockViewBuilder::viewMultiple()
:Comment #82
dawehnerThis is work to use render caching for the rest export.
Comment #83
Wim LeersIs this patch feature-complete? Can you list what still needs to happen for it to be feature-complete?
Comment #84
dawehnerNow also with some basic work to get rid of output caching.
Comment #85
dawehnerListed a couple of points.
Comment #90
dawehnerSome progress.
Comment #92
dawehnerdoh!
Comment #94
dawehnerContinous work
Comment #96
nagraj404 CreditAttribution: nagraj404 commentedDrupal Subuser Module
i am not able to assign automatically Parent roles to subuser , while creating sub user .
But same working in D6 but not in D7
Please help me
Comment #97
plach@nagraj404:
This is a completely unrelated issue, please use the search available at https://www.drupal.org/project/issues/subuser?categories=All to see whether your issue has already been responded, otherwise create a support request for the Subuser project at:
https://www.drupal.org/node/add/project-issue/subuser
Comment #98
plachImproved issue summary :)
Comment #99
plachA code review to get familiarity with last patches:
Why are we using inconsistent separators in the property name? Shouldn't this be
#cache_content_type
?Missing PHP doc
Huh?
Mabye just
$cache
, intended as a verb?Would it make sense to use an imperative form:
return_response
?This comment seems to need an update too.
Given that both methods are static and take the same arguments, do we really need to differentiate them? The actual return value seems an implementation detail to me. Could it be just a static
::handle()
method in both cases?In other places we use
$original
instead of$copy
:)Same as above
Oops :)
Comment #100
dawehnerIgnoring the reviews so far, just posting work on rss/feed related code.
Comment #101
plachPHP doc
Comment #103
dawehnerSome intermediate work.
Comment #107
dawehnerFixed hopefully most of the feed related failures
Comment #109
dawehnermh
Comment #111
plachThis name is a bit confusing: shouldn't it be
::getCacheMaxAge()
?Comment #112
dawehnerJust some general work.
Comment #114
Wim Leers#84 is very exciting!
#98: :D plach++
#112: Wow, down to 39 fails! Great work, @dawehner!
Comment #115
dawehnerMore work, now working on getting the contextual links working
Comment #117
dawehnerAlright, let's fix contextual links.
Comment #118
disasm CreditAttribution: disasm at AppliedTrust commentedI'm reclassifying this as a bug because of the security issue mentioned in #10.
Comment #119
Fabianx CreditAttribution: Fabianx for Acquia commentedRe-titling based on #118
Comment #120
Wim LeersClarifying the title further.
Comment #121
disasm CreditAttribution: disasm at AppliedTrust commentedI'm not an expert on views or caching, so I can't give an in-depth review of this, but here's a few things that jumped out at me:
since $item_text is no longer concatenated text, would it make sense to rename it something more explanatory of what it's storing?
Can this service be injected?
Why are these functions renamed with a prefix 'p'.
Comment #123
plachMissing PHP doc
WAT? :)
Missing PHP doc. Also, can we name this
getRenderCacheEntry
? Otherwise it seems we are returning the service.Cheater :)
Judging from the comment, this seems to require regular metadata bubbling. Should we use
::render()
instead?Comment #124
dawehnerThank you for your review, tried to address them.
This should remove quite some of the failures again, puh!
Looking forward to another 100K patch :(
Comment #126
YesCT CreditAttribution: YesCT commented@disasm tests renamed to start with p ... are skipped.
Comment #127
dawehnerHopefully one less failure.
Yeah that is a thing I do in order to speed up tests during debugging
failures.
Comment #129
plachhm
Sorry for being a PITA, I just realized this is missing type hints.
Why don't we define these on the
Page
display plugin?Missing
@param
, btw.Missing PHP doc :)
Same here :)
Comment #130
damiankloip CreditAttribution: damiankloip commentedThis would mean that a display can essentially override the max-age, if it's lower than the cache setting. That doesn't seem like the right behaviour?
Where is the use case of setting ->element['#cache'] ?
Is this a views specific thing? Can it live within the #cache array anyway?
Where and when do you opt out? Wouldn't the cache plugin control this? i.e. if it's 'none' do nothing.
Do we need a route parameter? Shouldn't Page always we using a route, and just assume this behaviour in this overridden method?
Is there a replacement for this functionality?
Is this the replacement for views_set_page_view?
Don't we create the item anyway? So we could make sure it's always an array?
Comment #131
damiankloip CreditAttribution: damiankloip commentedNice to see that fixed too :D
Comment #132
damiankloip CreditAttribution: damiankloip commentedI don't think we need to set this. Symfony does it for us. See Response::prepare(). Maybe it's better to explicitly set it anyway?
Comment #133
dawehnerThank you for your reviews!! This is just temporary work in order for damian to join the party.
Comment #135
damiankloip CreditAttribution: damiankloip commentedThanks! Was just going to start by adding a couple of assertions to help with our confusion over the RestExport headers, they actually work fine, as per my comment in #132
Comment #136
dawehnerLet's check whether #135 provides more failures
Comment #137
Fabianx CreditAttribution: Fabianx for Acquia commentedFrom IRC:
- When someone uses setPage() we need to change the cache keys (injected data)
- When someone does not use setPage(), we set the cache context for the pager (already in place)
Same for items_per_page and offset. (and later also contexts, but other issue as broken atm. anyway)
Also a key sentence by dawehner for anyone still confused:
(and don't have objects to work with)
This is key to everything.
If someone injects data in a view in that way, then the view itself is not affected and cache keys are fine, but if that should e.g. be cached again by e.g. block cache or panels cache or ... THEN the callers that injected the data into the view needs to bubble up that information that this view varies by that.
And the reason it is the callers responsibility for upper levels is:
- It is the caller who accesses e.g. the request and gets the 'page' parameter.
Rule of thumb:
"Whoever uses an external resource to make a decision or command a decision on another class and that decision varies the output, needs to specify the variation criteria, i.e. the cache context".
Comment #139
plachComment #140
dawehnerFixed
Good point, moved it, btw. storm can do it.
Fixed.
Yeah that is the central problem, we want to have a min cache age, see issue summary.
For example when you want to vary things via a cache context, when you don't have the view object yet
#cache_properties is a thing which exists, but we want to store those additional information in order to make contextual links working outside of a loaded view object. Do you think this is okay?
Well, I thought that this method is more of an API function people could use. Cache plugins would opt out on top of that. I see your point thought, mhhh
Well, technically its defined on the parent class, so additional parameters can just be optional. For other display types though, it does not make sense to add $route.
Yes this is the replacement, now on the Page display class.
The aggregator implementation is a little bit odd:
Do you think we should convert
$item->description
there to be a render array?Comment #142
plach<pita>
"Setups", missing trailing dot :)
</pita>
Comment #143
dawehnerThank you a lot for your quick reviews!!!!!!!
Wow, this debugging session needed to much time, but yeah! for IDEs show memory addresses
Comment #145
Wim Leers$view->element
strikes again!Comment #146
dawehnerWorking on test failures atm, some cleanup in the meantime.
Comment #147
dawehnerWork done on #2463959: dawehner testing issue
Comment #148
almaudoh CreditAttribution: almaudoh commentedNice IS picture. Wish all IS had pix.
Comment #149
dawehner"Some" work in the meantime ...
Comment #151
dawehnerIts green!
Comment #153
dawehnerThis time for real
Comment #154
Fabianx CreditAttribution: Fabianx for Acquia commentedNice work!
Comment #155
jibranAwesome it's green.
Any reason for this change and removing it from CommentLinkBuilder CLB?
Why are we injecting renderer if we are removing calls of drupal_render_root?
Can we have comments for these changes? :)
Can't we just use REQUEST_TIME instead of
(int) $this->view->getRequest()->server->get('REQUEST_TIME')
Doesn't make sense at all.
Comment #156
dawehnerThank you jibran for your super fast review response time!
I'll put that into the issue summary to explain that to others.
Good catch! Fixed that ...
Well, I just wanted to do it the right way ... see #2459155: Remove REQUEST_TIME from bootstrap.php
Fair :) Let's update it a bit.
Comment #158
dawehnerDone
Here is more test coverage, puh, things aren't trivial always.
Comment #159
dawehnerUpdating the remaining tasks ...
Comment #162
webchickComment #163
dawehnerWent through the list of plugins defined in views.
Bugs I realized:
Comment #164
plach(Crosspost, reviewed #158)
Additional test coverage looks great. Some minor stuff:
Ha!
PHP doc
Can we explain that we are removing one item because we display only ten?
EntityTest::create()
?Why? To avoid random failures?
PHP doc
Mmh, no
Hm
Double call
Comment #165
plachWondering whether #2493033: Make 'user.permissions' a required cache context will make all these unnecessary. It's not critical, though.
Is this opting out from display-level render caching or whole render caching? I'm asking because row caching should still work.
Comment #166
plachBlocked on #2498857: Support min-age in render caching, not postponing since we have other areas to focus on.
Comment #167
dawehnerThanks again for the quite quick review!
We discussed about min age and moved it first to its own subissue: #2498857: Support min-age in render caching
Good point.
Well, to have something which can be used between requests, for example inside the test code and the actual site.
Let me explain that a bit better.
You are absolutely right, this just takes care about the order.
Ah, let me manipulate the current request in the test.
Comment #168
plachForm the interdiff it seems a few items from #164 were not addressed: 1, 7, 9.
Comment #169
dawehnerWell, 1, 7 got addressed in #163 already and 9 is part of the interdiff:
Comment #170
dawehnerOpened up a follow up around the discussion @berdir, @catch, @msonnabaum, @plach had in IRC: #2498969: Time-based cached views get invalidated too frequently
Comment #171
plachSorry, I need a pair of glasses :)
Comment #173
dawehnerThere are signs that you sit too much in front of the computer, see interdiff
Added the test coverage for random sort, role and permission access checking.
Comment #175
dawehnerFixed the failures of #167
Comment #177
dawehner.
Comment #178
Wim LeersAwesome work! :)
57 files changed, 1158 insertions, 589 deletions
, but most of that is due to added test coverage.Many nits, many minor things, a few substantial things.
Questions:
#cache[keys]
in several places. (Several concrete questions in the review below.)CacheablePluginInterface
, hence forcing every handler/plugin to consider their cacheability impact? It seems that in this patch all remaining handlers/plugins (that didn't already provide cacheability information) now provide this metadata too.Good catch! :)
This is weird… for every comment field, if there is a 'links' display component in the entity's 'RSS' view mode and the first comment is published, then we a link to basically "http://mysite.com/node/X#comments".
WTF? :P
Would be great to document why it works this way. I know it's pulled out of
CommentLinkBuilder
, but there's zero documentation right now to explain the if- and for-statements.Nit: Two newlines, should be one.
Nits:
s/rest/REST/
s/with enabled views render caching/with views render caching enabled/
Nit: s/cache entry/cache item/
Nit: 2 newlines, should be one.
Nit: s/array/string[]/
Nit: extraneous newline.
Hah, clever use of that service for testing :P
Yay! More precise cache contexts! Better cacheability!
\o/ Yay!
Another excellent catch.
This looks weird. Perhaps correct, but then the formatting for the added line is definitely off.
s/Setups/Sets/
It'd be better to not have explicit code for all this, because it makes it harder to add more cacheability metadata.
Besides that, there are indentation problems here, as well as using
Cache::mergeTags()
for merging cache contexts.I think all of this can be simplified to:
?>
The comment is talking about cache contexts, but the code is dealing with cache keys.
Two spaces after the equals sign.
Why unsetting
#cache
? Why not let cache tags be bubbled, for example?Needs a comment explaining the rationale.
Two newlines here.
Why set the
#cache_properties
separately?The second line needs two leading spaces.
Ok, so this first creates an empty response, so that the rendering logic can access the
Response
object via the#response
property on the render array.Why? Needs documentation.
Nit: two newlines, should be one.
… and this is why we really should do #2464427: Replace CacheablePluginInterface with CacheableDependencyInterface; then Views plugins/handlers can specify a
max-age
!IMHO needs a @todo for that, to remove this direct manipulation of
$this->view->element
.Nit: s/array/string[]/
Haha, love it :D
Nit: 80 cols exceeded at line 35.
Nit: s/max age 0/max age of 0/
Nit: accidental change that should be reverted.
Unused 'use' statement.
Is this really necessary here? Could use a comment explaining why if "yes".
Difficult to parse.
Comment #179
plachÜbernitpick (trying to win the "nitpick of the month" context):
It would be more readable if this line were below the comment so we'd have two symmetric blocks of 4 lines implementing symmetric logic :)
Same here
dawehner++
Can we add a note that this is not supposed to opt out from row render caching?
Comment #180
dawehnerGiven that we don't change the behaviour of the time based cache plugin in this issue, we should do that as part of #2498969: Time-based cached views get invalidated too frequently
Answered ...
Not as part of this issue, but still, we need to figure out the different levels, like render vs. result caching ...
No there is in HEAD, see review section, its pretty epic, that it actually works.
Well, that was simply a test failure ...
I'll just try it long enough, until its part of the english language.
Well yeah, there is no reason to for example post_render_cache both on the view element and the view_build twice, you just need it either on one or the other.
That is pretty cool!
This is tricky, but well, we want to certainly unset the cache keys, as we don't want the element to be cached. Let's try that.
Fixed that
No idea, I'll just ignore that now :P
Added ...
Comment #182
dawehnerLet's fix it ...
Comment #183
Wim LeersInterdiff review:
These can be chained.
You can even chain it with the instantiation:
I think a more accurate comment would be something like:
Patch review round 2. Mostly nits, but also suggestions for better function names. IMHO the proposed function names significantly improve understandability.
buildResponse()
Also: missing docs.
s/onto/on/
setCacheMaxAge()
would be better IMHO, but would probably be internally inconsistent.Applies the cacheability of the current view display to the given render array.
The render array with updated cacheability metadata.
applyDisplayCacheability()
Both occurrences inherit docs, hence neither actually documents it :)
s/Setup/Set up/
s/setup/set/
s/Content-type/Content-Type/
s/array array/page render array/
:)
s/render array/page render array/
(For consistency with
setPageRenderArray()
's docs.)Comment #184
dawehnerAddressed the reviews of #179 and #183. Thanks a lot of for the both great and quick feedback
Hei, I think this is a total valid point! Sorry no, award for you ;)
Sure
Have a look at the method, it doesn't set the max age, but rather it determines the max age for
::cacheSet()
Went with
applyDisplayCachablityMetadata()
Comment #185
Wim LeersOnly small things left now. As far as I'm concerned, once these are addressed, this is RTBC. plach is going to do his final review round tonight/tomorrow — I defer the actual RTBC decision to him, since he knows Views better than I do.
So glad that this is so close now! :)
s/response/Response/
s/JSON or XML./JSON, XML, or some other format./
I think
ResponseDisplayPluginInterface
might be a better name?Missing trailing periods.
s/build/built/
So much better!
s/THe/The/
The page render array.
Comment #186
dawehnerYeah, another round
Comment #187
damiankloip CreditAttribution: damiankloip commentedIt seems that ViewExecutable::render() has to do a lot more every time it is called than it used to. With output caching much more was bypassed I think. How much worse is this for render time for a cached view? or is it better because of other benefits?
Comment #188
damiankloip CreditAttribution: damiankloip commentedYou can remove the getOutputKey() getter and the property now too.
Comment #189
amateescu CreditAttribution: amateescu as a volunteer commentedHere's a not so useful review, mostly because I don't know views' internals that well :)
I think the doc standard says to use only lowercase here, so "|false".
We actually implement only
hook_entity_view()
, and we're missing the()
suffix.Not exactly sure why these are useful, but if they are, let's capitalize the second string as well: Second page.
It looks like the renderer service that is injected in this class is no longer used anywhere, so we can remove it, along with the
use
statement at the top of the file :)Unneeded empty line :P
I think the trailing ");" needs to go on a separate line.
Looks like the $args argument is optional, let's note that in the doc.
Same here, $args is optional.
Should we do a
$request_stack->pop()
at the end like we do inassertViewsCacheTags()
above?Comment #190
dawehnerTalked with damian about it, and we agreed that the cached case is much faster now, as we don't even have to load the view object.
Good idea!
Ha, good point.
Yeah I really needed it in order to figure things out. I think people will need it in the future as well.
I decided to not do that, in order to keep things as simple as possible ..., I mean its not needed as part of the test.
Comment #191
Wim Leers+1 to
pass()
to make tests more debuggable. Maintainability++.Comment #192
dawehnerSeveral additional things:
Comment #194
dawehnerLet's be green again.
Comment #195
dawehnerAdded a follow up to add test coverage for render caching on ajax requests ... which doesn't work at the moment, see #2500313: Add views render caching on views ajax requests
Comment #196
plachMissing default specification.
Missing issue link :(
Are we doing this twice to test caching? If so can we add a comment?
And... surplus blank line!
(I'm an awarded nitpicker now :)
Comment #197
plachHere's a final review, doing some manual testing now.
Is this used?
Shouldn't this be
::getCacheMaxAge()
?This is a bit convolute. What about the following?
"In order to be rendered cached, it includes cache keys as well as the data required to load the view on cache misses."
I assume $route is optional only for compatiblity with the parent method. Should we throw an exception if it is not defined?
What about this?
"[...] some other format requiring to return a response directly."
Missing trailing dot in the second line. Also, do we use "@see" in inline comments? Shouldn't it be "See"?
Comment #198
plachOk, I did some manual testing with my various test views and things seem to work fine even in complex use cases, test coverage++
I did a quick profiling round and with the usual test view with 8 fields and 50 rows. These are the results:
I couldn't compare HEAD vs PATCH because I had to reinstall to apply the patch. However disabling display render cache is more or less equivalent, handle results with care ;)
That said, numbers look pretty good :)
Once the last reviews are addressed, I'm ready to RTBC this!
Comment #199
dawehnerLet me quote https://www.drupal.org/coding-standards/docs
Sure, let's add one #2500701: Make it possible to specify the page in the basic views renderable
No, we are calling out once to
assertViewsCacheTagsFromStaticRenderArray
and once toassertViewsCacheTags
. Added one comment.No, let's remove it again ...
Well, the thing is that this method already exists, and is used for render caching, not for the result caching ...
Sure
Good idea!
As it turns out, its the expected behaviour, so we don't need to link to the other issue.
Comment #200
damiankloip CreditAttribution: damiankloip commentedI'm not sure I get the profiling comparisons. I would always expect a warm cache to be way faster when compared to cold caches so that seems moot?
A better comparison would be warm caches with this patch and before with output cache?
Unless I miss something of course :)
Comment #201
Wim Leers#200: I was confused too at first; asked plach for clarification.
Comment #202
damiankloip CreditAttribution: damiankloip commentedYes. That is exactly how I interpreted it. I still don't get it. I stick by #200 :)
Comment #203
plachAwesome!
If we happen to reroll this
\InvalidArgumentException
would look more appropriate to me :)Well, I could not apply the patch to my existing installation as it failed badly, so I had to reinstall. I tried to recreate more or less the same situation in the new installation (500 nodes, same test views) but it's not a fair comparison, at least wrt my old profiling runs. So I decided to compare row render caching (default scenario in HEAD) vs display render caching (default scenario with patch). I thought that getting a sense of what caching is bringing us had a value.
Anyway, here is the profiling case suggested by Damian (HEAD vs PATCH) on the new installation, so not exactly comparable with my earlier profiling runs here and in the parent issues. Still good :)
Comment #204
damiankloip CreditAttribution: damiankloip commentedAWESOME! Thanks plach :)
That is what I was after, this now show the comparable (as much as possible) benefit of using render caching. It shows the improvements by render caching working a level above the actual view laye, and therefore not needing the view at all for cached output! This is RTBC from me too.
Comment #205
fgmCongratulations to all. Just added Plach's suggested exception change. I notice we don't have a test for it : shouldn't one be added ?
Comment #206
catchNote that 38,000 function calls is the current more-or-less baseline for a full HTML response at the moment (i.e. just node/1 with the page content type takes that much too), so this really shows no additional cost from Views at all with a warm render cache.
Comment #207
dawehner@plach
After manually doing some profiling for /admin/content I see similar results, even the absolute numbers are totally different
I see something like 120ms with the patch vs. 180ms with old tag based caching.
Btw. I tried to compare that with old D7 numbers.
Went with the same test case /admin/content (admin_views module in use), we achieve like 150ms without time based caching and 40ms with time based caching.
One thing which shines out of the remaining milliseconds is potentially #2500979: Try to optimize Safemarkup::set()
Comment #208
dawehnerAdded a follow up for a potential speedup once we have render cache on ajax requests
Comment #209
plachI have a crappy MAMP stack I've never spent 1 second to optimize so, yes, my absolute numbers are not very meaningful, only percentages and function call numbers.
Well, time-based caching from admin/content really does not make sense, so we are faster than D7 now :)
Anyway, any further improvement is welcome obviously ;)
Comment #210
Fabianx CreditAttribution: Fabianx for Acquia commentedCould WimLeers and me be added to the commit credit list, please? Thanks!
Comment #211
alexpottHow come - is the renderer not injectable?
Why is this necessary... it should already be that. The code below is how $server is populated.
If it is necessary it needs a comment.
Not used
As per @fgm not tested.
The world is better off not knowing :)
Can remove the use statement for SafeMarkup now.
Not used.
Can remove the use for Request now.
Remove use SafeMarkup and use NotFoundHttpException...
Not used
return
Need to update function's doc block.
I think the new ResponseDisplayPluginInterface makes the returns_response annotation obsolete. Couldn't we set this option on the route if the display implements this interface without the annotation?
Comment #212
Fabianx CreditAttribution: Fabianx for Acquia commentedThe removal of views_get_page_view / views_set_page_view is an API change however and needs a change record - unless I am mistaken.
Overall this whole thing could use a change record on how to convert code to be fast and take care of the new pattern.
Overall: Great patch!
Comment #213
alexpottre #212/@Fabianx - yes it needs a CR but not for Drupal - for Views :)
Comment #214
alexpottAdding @Fabianx and @Wim Leers to the commit credit
Comment #215
lauriiiRerolled the latest patch
Comment #216
dawehnerIts a static method where this is used ...
Well its about time() vs. REQUEST_TIME(). Added it to BrowserTestBase as well and documented it.
Sure, let's add a simple one ...
Fixed ...
Added a follow up for it, ... it might require more changes than obviously needed: #2501389: Explore whether the return_response annotation can be removed and replaced by \Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface
Working on the CR now.
Comment #217
dawehnerAdded two different change records ...
Comment #218
dawehnerForgot the additional test
Comment #219
lauriiiPatch on #218 addresses all the points on #216
Comment #220
plachRTBC +1
Comment #221
catchI haven't managed to review the latest patch all the way through yet, as far as I read looks (very) great though.
One question: with render caching/tags enabled by default, is there any use now for the query/result cache? The main reason to have those was when output caching wasn't safe, but you didn't want to run the query each time. This was quite a lot of the time in 7.x.
Now that render caching is reliable, I'm not sure how useful those are any more. Obviously this is follow-up if at all.
Comment #222
dawehnerThat statement is not true, at least according to the patch
Regarding result caching ... the amount of variations of render caching is potentially much higher than for result caching, as all the rendering / field access etc.
potentially even makes things not cacheable. On the other hand, result caching is way more limited, how it can vary. Its mostly pager + exposed form + arguments + node access.
Comment #223
catchRight I think we should aim for this though, then we could retire https://www.drupal.org/project/views_cache_bully
Yes very true, most result sets/queries won't vary by permissions etc. so the separation still makes sense.
Comment #224
plachMmh, I didn't realize the tag-based cache plugin was not the default yet. Any reason not to do so? Maybe because we should update all the shipped views? We should open a follow-up to do that, unless we want to keep none as default, but that's not what the proposed solution states.
Comment #225
catchYes I also thought we'd make it the default here. I think that's fine as a follow-up just because this patch is plenty to review by itself. But we probably want to make that critical/upgrade path - we may not allow ourselves to update the shipped views on existing sites after release.
Comment #226
dawehnerWell, in my opinion you could so easy just cache too much, like when you have an exposed filter with a text field, thought yeah maybe this is something not to care about.
This patch currently just enables it for the frontpage view.
Comment #227
olli CreditAttribution: olli commentedWe only need comment manager when $view_mode == 'rss' && $display->getComponent('links'). Any reason to not use the HIDDEN constant in the new code? After promoting a basic page the ->get($field_name) gives me "Twig_Error_Runtime: An exception has been thrown during the rendering of a template ("Field comment is unknown.") ".
Could we instead return early when view mode is 'rss' like with search and print view modes?
'url.query_args.' -> 'url.query_args:'
Comment #228
Berdir1. Nice catch, that code absolutely needs a $entity->hasField($field_name) before trying to access it.
Comment #229
dawehnerGood catch. I went with adding
$entity instanceof FieldableEntityInterface
as well ...Let's put that into their own dedicated issue #2501905: \Drupal\views\Plugin\views\argument_default\QueryParameter should specify a more specific cache context in order to always add test coverage and what not ...
Comment #231
olli CreditAttribution: olli commentedCommentRssTest passes locally with this
Comment #232
dawehnerThank you olli!
Do you consider this patch as RTBC now? I had a look at the interdiff, that this looked pretty sane ...
Comment #233
olli CreditAttribution: olli commentedyes!
Comment #234
alexpottUnfortunately needs a reroll...
Let's get a followup for comment to add a third party setting to views modes this is beginning to look like it is something that should be configurable.
Comment #235
catchOpened #2502255: Enable tags cache plugin by default for Views as a follow-up.
Comment #236
dawehner--d.o
Comment #238
dawehnerComment #240
alexpottAdded olli's fixes from #231 back and fixed the debug messages because the order of arguments made no sense with the messages.
Comment #241
dawehnerOh yeah, sorry olli!
Comment #243
catchCommitted/pushed to 8.0.x, thanks!
Comment #244
plachYay!
Comment #245
Fabianx CreditAttribution: Fabianx for Acquia commentedCongratulations!
Comment #246
Wim Leers#206:
Indeed! :) This was one of the key goals as well, of course: not only security (i.e. use bubbled cache contexts rather than hardcoded cache context-approximations), but also pure performance, i.e. truly do no work other than building a super thin super cheap render array and then doing a render cache hit. No more initializing of all Views handlers & plugins. :)
#211
+1!
#222: +1 to keeping results caching. But I wonder if it would be better to have that in contrib rather than in core? Not sure.
#223: +1 to having
views_cache_bully
in core, and it actually already happened in the mean time: #2502255: Enable tags cache plugin by default for Views. :O :O :)Amazing work, dawehner & plach!
Comment #247
Wim LeersWhile working on something else, I noticed a tiny bit of clean-up that was missed here, filed a follow-up with patch: #2507459: CachePluginBase and subclasses no longer need Renderer/RenderCache services injected (pure deleting of code :)).
Comment #249
penyaskitoPublished both change records.