Problem/Motivation
Follow-up to #2342045: Standard views base fields need to use same rendering as Field UI fields, for formatting, access checking, and translation consistency.
Blocking critical #2381277: Make Views use render caching and remove Views' own "output caching"
Will help with performance.
https://www.drupal.org/project/views_row_cache exists for 7.x contrib.
Caching views rows would mean we'd get similar behaviour on cache misses to full entity rendering:
1. Cache hit - single cache get from views output cache
2. Entity list tag is invalidated
3. a. Both the row for that entity, and the views output cache misses
b. However, now all the other rows rendered by the view are a cache hit
Proposed resolution
Remaining tasks
Things to look at:
- 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?
Once we have a resolution, decide if we need a change record.
User interface changes
API changes
Comment | File | Size | Author |
---|---|---|---|
#152 | cache_field_views_row-2450897-151.patch | 75.44 KB | plach |
Comments
Comment #1
Wim LeersComment #2
Wim LeersThis only affect Field views, not Entity views.
Comment #3
jibranViews row has nothing to do with views field display. Views row can have entity display plugin in it.
Comment #4
plachNot sure about D8, but on the D7 project where I developed VRC, you got an improvement in the response time of ~25% with warm caches on a page with a single table view with 25 items per page. Another page with a table view with no pager (~400 items), went from ~4500ms to ~400ms.
This obviously depends heavily on the view, but you start seeing some real gain when you have a fair amount of rows. We'll need to do some profiling, but I wouldn't be surprised if it turned out that we have a gain only for views having at least X fields and Y rows in the result.
Comment #5
Fabianx CreditAttribution: Fabianx commentedComment #6
yched CreditAttribution: yched commentedWondering how this would play with combined with #1867518: Leverage entityDisplay to provide fast rendering for fields, which should leverage entity cache on views rows ?
Comment #7
dawehnerWhile I think having caching per row could dramatically improve stuff (yeah to VCR), I think we should still try out #1867518: Leverage entityDisplay to provide fast rendering for fields and more potential issues in there.
Comment #8
Fabianx CreditAttribution: Fabianx commentedWe should absolutely try out #1867518: Leverage entityDisplay to provide fast rendering for fields, because the best time spent is the time you don't spent :-D.
If we can make something 150 ms faster thats absolutely worth it.
Comment #9
plachDo the numbers we are seeing in #1867518: Leverage entityDisplay to provide fast rendering for fields mean this is critical too?
Comment #10
Gábor HojtsyOn criticality I think it depends on how much this helps with performance? Do we have a feeling ahead of time? Looks like #4 and the results of #1867518: Leverage entityDisplay to provide fast rendering for fields make this critical, no?
Comment #11
Fabianx CreditAttribution: Fabianx commentedI think for now this is critical as even a 40% performance regression at this point is problematic ...
Comment #12
effulgentsia CreditAttribution: effulgentsia commentedTagging "blocker" per #2381277-20: Make Views use render caching and remove Views' own "output caching".
Comment #13
YesCT CreditAttribution: YesCT commentedComment #14
xjmDiscussed with @effulgentsia, @alexpott, and @webchick. Based on #4 and #8 and the critical performance criteria, this issue should definitely be critical. @effulgentsia pointed out that while we also have an issue to cache the full view render, there are many, many operations which will invalidate the cache for a whole view, resulting in cache misses, so it's still worth caching at the row level, especially given that we've introduced a significant performance regression.
Comment #15
plachWorking on this
Comment #16
Fabianx CreditAttribution: Fabianx for Acquia commentedSo after discussing this in IRC the outcome is:
Row based view caching is _very_ difficult to implement properly, because there are no rows per se, but all is rendered via the theme layer.
The best we can do is:
- Use field based render caching, store in StylePluginBase::rendered_fields
- Ensure instead of !isset() for renderedFields() a render-only-what-is-missing approach is used
- Optimize from working field based render caching to load the whole row and pre-populate rendered_fields with a cache key of 'view:name:display:row_1'
- Store at the end when rendering is finished by setting the cache for each newly rendered row.
Comment #17
Fabianx CreditAttribution: Fabianx for Acquia commentedI have a first proof-of-concept based on #16, but it is really ugly. BUT IT WORKS!
Views really needs #2466585: Decouple cache implementation from the renderer and expose as renderCache service OR at least public cacheSet / cacheGet functions on the renderer.
Trying to use #pre_render works, but is really ugly and need to set cache twice ...
Limitations are:
- All work done in preRender is still done. (Hence with the entity view display patch we need to do the cacheGet way earlier, but the cacheSet later).
- SafeMarkup needs to be set afterwards manually.
- cache->set() is called twice
- Way too much copied code ...
- The cache keys are not including $view name and display id, etc.
Explanation of some points:
- Metadata is pushed to the stack and always in the top entry of $data, this matches the previous behavior of pushing the data to stack when rendering the fields, so is not a problem (that is complicated ...)
Comment #18
Fabianx CreditAttribution: Fabianx for Acquia commentedLets still see what breaks :).
Comment #20
plachGreat start!
The attached patch adds some clean-up, implements cache keys and tags handling and refactors the
EntityFieldRenderer
introduced in #1867518: Leverage entityDisplay to provide fast rendering for fields to lazy render field values instead of pre-rendering them. The performance boost is huge: with warm caches response is ~55% faster with a table view with 50 items.I think we could make this even faster if we had a way to a multiple cache get, as this would also allow us to inform the system of which rows need actually to be built, instead of building them all every time we have a cache miss.
This patch includes the last one of #1867518: Leverage entityDisplay to provide fast rendering for fields, attaching also a diff against that.
Comment #22
dawehnerThat sounds something which should have a general API ...
its sad that we have to figure that out for ourselves.
Alright, so a) we have to check whether there is _entity, its a false assumption that we always render one ... Don't we have to add the langcode of the translation for example as well? We also have to take into account the fact that we might have an aggregated view.
It is amazing that we can cast random php objects into arrays.
Comment #23
Fabianx CreditAttribution: Fabianx for Acquia commented1. / 2. Yes, indeed: #2466585: Decouple cache implementation from the renderer and expose as renderCache service is the issue for that. Once #2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption lands I propose to just make the cacheSet / cacheGet methods public on the renderer in this issue as a proof-of-concept and convert to using that ...
Comment #24
xjmComment #25
plach(Partially) addressed #22.3, I'm not sure we need special care for aggregation since the related fields will end up in the row data automatically.
Comment #26
plachComment #27
plachReuploading...
Comment #29
plachUpdated numbers:
Comment #30
dawehner@plach
It would be nice if you could post reviewable patches
Note: We have an open issue that we don't support relationships here.
Comment #31
plach@dawehner:
What do you mean? In #25 I posted https://www.drupal.org/files/issues/cache_field_views_row-2450897-25.txt which includes only the changes introduced here.
Comment #32
plachIf you are referring to #2446681: Error "Column 'langcode' in field list is ambiguous" thrown due to TranslationLanguageRenderer not rendering a field from a relationship, that seems to be fixed in HEAD now.
Comment #33
dawehnerOh, I'm sorry. Great work!
#2457999: Cannot use relationship for rendered entity on Views is the issue I tried to refer to.
Here is a naive question: Why can't we call out to
$this->preRenderRow()
directly? It seems to be a layer of indirection which is not obvious, why we use it.You are right about the fact that we don't have to care about aggregate itself. Note: Can we use sha256, otherwise some people will complain about the usage of md5 again, I think its just not worth to start some fight here again :(
Comment #34
Fabianx CreditAttribution: Fabianx for Acquia commentedJust a short note that this is _double_ critical:
We don't have any filter caches anymore in favor of render cache, but that means that views currently do not have any text filter caches at all ...
Comment #35
plachThat way it is not called if render cache is available. However that code will go away once #2466585: Decouple cache implementation from the renderer and expose as renderCache service is done.
sha256
will introduce very long cids, would it make sense to make the hashing function configurable? VRC has a variable for that.Comment #36
Fabianx CreditAttribution: Fabianx for Acquia commented#35: I think hash('sha256') is fine. We use automatic cid stripping to a hash also in the cache system now and I think we also allow 255 chars. Let's check.
If someone can RTBC #2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption or ping Wim to RTBC it, we can make progress on the renderCache service and hack it here by making the render thing public as a hack instead.
Comment #37
dawehner#35
Yeah, that really sounds like quite a micro-optimisation.
Comment #38
plachI won't work on this until tonight...
Comment #39
Fabianx CreditAttribution: Fabianx for Acquia commentedSpoke with plach:
- Test failures related to admin links we can ignore and just disable VRC on paths that are admin routes (same as smartcache)
We already have 2 critical issues open for such links:
* https://www.drupal.org/node/2351015
* https://www.drupal.org/node/2335661
but that is a general problem and can be easily created in HEAD by putting any admin content listing into a views block, then caching that block => BOOM
--
Therefore those should not block VRC and a route check should be fine for now.
Comment #40
dawehnerI'm looking at those test failures in the meantime.
Comment #41
dawehnerSome test fixes.
While working on it, we had to open #2473873: Views entity operations lack cacheability support, resulting in incorrect dropbuttons
Comment #43
siva_epari CreditAttribution: siva_epari commentedPatch didn't apply. So rerolled.
Comment #45
siva_epari CreditAttribution: siva_epari commentedSome fixes to reduce test failures.
Comment #47
plachRerolled on top of the latest code from #1867518: Leverage entityDisplay to provide fast rendering for fields and performed some clean-up.
Comment #48
plachComment #49
Wim LeersWhy is this necessary?
Why not just call
Cache::invalidateTags()
?Comment #51
dawehnerWell, many of them are needed because we manipulate the view and execute/render it again ...
Well, I personally don't like those hiding wrappers
Comment #52
Fabianx CreditAttribution: Fabianx commentedWow, amazing how small this patch now is when one thinks all the renderer / renderCache overhead removed.
Comment #53
plachJust a reroll for now
Comment #54
plachComment #56
plachThis should fix some more test failures.
Comment #57
plachDone, for now
Comment #58
plachI asked Fabian to have a look to
ContactLinkTest
to check that my fix is correct and render cache is working as expected. Here is my test db, the test view can be found at/test-contact-link
, you just need to display the view with the various admin/contact/test users and check render cache entries are the expected one.Comment #59
Fabianx CreditAttribution: Fabianx commentedThis should be correct ...
What is wrong about this?
This is probably wrong.
Cache tags are for invalidation and not for variation.
Cache contexts need to be used for more invalidation and is probably what is missing here ...What I meant here was:
"Cache contexts need to be used for more variations and is probably what is missing here ..."
Comment #60
plachI just reverted those changes, they were breaking tests even more. Now we have what's in HEAD.
It was just a quick and dirty way to add entity ids in the row data, I will find a cleaner way.
Comment #62
Wim LeersWoah. Why is this only necessary for the
set()
case? Why not also for thedelete()
case?Also: WTF! I had no idea this existed. Looks like this is the last remnant from before Users were fieldable Entities.
We're definitely missing test coverage in this area then.
I think we need a very thorough, as-clear-as-possible explanation for why this needs to implement its own render caching.
Comment #63
Fabianx CreditAttribution: Fabianx commented#62
1: Yep, looks like it, good catch by plach.
2)
It is pretty simple.
Because how views works, there is never something that is operating on a whole row, but on fields.
What we do is:
We store the whole row with the whole row cache metadata, but keep the output of the individual fields, which are rendered to a string.
A cached row render array looks for example like this:
Therefore we create the exact array again that is needed by views in the next steps.
This is 100% comparable to how entity render caching works (it also caches the whole entity with all fields output).
We cache the whole build here as well, just that in this case we need the individual fields output for future usage, by e.g. the table plugin.
Comment #64
plachThis should address the reviews above and fix the last failures. Now we just need to clean-up our code once #2466585: Decouple cache implementation from the renderer and expose as renderCache service lands and provide some additional test coverage since most Views tests are running with cold caches. Manual testing results are encouraging though, and the
ContactLinkTest
is actually testing the new code and is green. The bot patch includes #1867518: Leverage entityDisplay to provide fast rendering for fields as usual.Comment #65
plachNot sure why we lost the first two hunk in the interdiff, probably in #47. This demonstrates we need specific test coverage.
As a bonus performing some clean-up (last two hunks).
Comment #68
plachThis reverts the
UserData
fix: theUserData
service is normally used byUser::postSave()
/User::postDelete()
, hence we would be invalidating tags twice for each user write. This is in internal implementation that should not be used as a public API, hence I think it's fine to just fix the test instead.Comment #69
Wim LeersSo you're saying that this first line is abusing an internal API, in a way that no contrib module ever would (they would modify the data on the User entity and call
->save()
on that User entity)?If so, then I agree that the added invalidation line there is acceptable.
Comment #70
Fabianx CreditAttribution: Fabianx commentedThis review was for an earlier patch (#65), but I did not see that it conflicted, hence re-posting now.
Why do we need to calculate that separately?
Lets call that something like:
alterRowCacheHash/Keys/Data?
That is else really confusing ...
Besides those two remarks, this looks great as usual!
Comment #71
plachThis should address #69 and #70.
I also separated the code computing row identifiers to its own method, as I think that will be useful to compute cache keys for the full view render cache.
Actually I realized only a couple of user data properties are handled in
User::preSave()
, so this service is supposed to be used as a public API. Created #2477903: User data service does not take cache tag invalidation into account to work on a proper fix.Comment #73
plachLet's try to remove displays altogether
Comment #74
plachI tried to go ahead and apply #2466585: Decouple cache implementation from the renderer and expose as renderCache service but I have some trouble using the new API and make things work. I had to perform some adjustments. Attaching my current code, it would be good to have some feedback before doing more work.
Comment #75
Fabianx CreditAttribution: Fabianx commentedOkay, you will still need to use the #pre_render pattern, but that is only due to how views work.
The code needs to look something like:
I can provide a working implementation without problems - if you want.
Comment #76
Wim Leers#2466585: Decouple cache implementation from the renderer and expose as renderCache service landed, this can now be rerolled on top of that.
Comment #77
plachThis should address #75-#76 plus some clean-up to make it easier to get row identifiers, which may turn useful in #2381277: Make Views use render caching and remove Views' own "output caching".
There is still some fix needed as
RendererCacheInterface::set()
does not preserve any actual render array value, hence there is no way to store field markup. For now I used an ugly hack, but I hope there is a cleaner solution.Finally, yesterday I started implementing test coverage. The bad news is that we still have some broken stuff, the good one is that if cacheability metadata is correctly specified things seem to be working properly also in fairly complex use cases.
I restored the changes Daniel did to the
ContactLink
field handler and it seems to work more or less correctly. There is still one problem, although the current contact link test is not exposing it:ContactPageAccess
specifies different cache granularity depending on the execution context, for instance users are not allowed to access their own contact page. If you list all users with a view displaying only a contact link (not very sensible) every user should see her link missing and all the other available (provided she has access to contact forms). However this not always work because an item may be cached with auser.permission
cache context, even if the current user would requireuser
cache context.Probably it would be better to skip all this mess and just use placeholders, like we re doing with node links, for all field handlers requiring per-user granularity.
Comment #78
Wim LeersSo this is why you said:
But
RenderCache::set()
doesn't modify the$elements
it is given anywhere. So… I don't understand what is being lost/not preserved.Can you explain this a bit more?
Here and elsewhere: s/RendererCacheInterface/RenderCacheInterface/
Typewise, this is:
How can this work? :)
That sounds like a bug in
ContactPageAccess
, or this patch not correctly supporting cache redirection (see the super long comment inRenderCache::set()
regarding cache redirection).Upon further thought, I think I see what you mean. Let me describe the situation that I think more clearly explains it, then you can say this is indeed the problem you're describing or not:
user.permissions
. They are render cached (row cached, thanks to this patch).user.permissions
as their cache context, notuser
.In other words:
actually needs to be executed for every user, but is not in this case.
That is a great, great, great find!
I wonder how big of a general problem this is. At first sight, it looks like in cases where an access result may be per-user, it always needs to be per-user, because otherwise this exact problem can always happen. Fortunately, there are only about a dozen
::cachePerUser()
calls.Comment #79
plach1/3:
RenderCache::set()
does:and caches
$data
(after some massaging), which means only$data['#markup']
is preserved between different requests (aside from cacheable metadata). The current code is storing and array instead of a string in$data['#markup']
(aw) in order to preserve individual field markup.4: You are right, with the exception that since we are talking about fields, we just have an empty field and not an entire row go missing. Hence you can simplify the example and not introduce paging.
Yep, I came to the same conclusion. If this is true it needs to be documented very clearly.
As I said, for the views field case I think using post_render_cache placeholders as suggested by Fabian would be easier, and would also keep down the number of cache entries.
Comment #80
Wim LeersBut I'm still not quite sure what you mean. Yes, only the following keys are preserved in the render cache:
'#markup'
,'#cache'
,'#attached'
and'#post_render_cache'
. This is very much intentional:'#markup'
), plus its bubbleable metadata (everything except'#markup'
)Having said all that and re-read the relevant part of the patch/interdiff, I think I now understand your problem: you want to cache "per row", but you want to be able to pull out individual fields within a row. The Render API only caches at the levels (the subtrees) where you specify
[#cache][keys]
. Since you only specify it at the row level, it only caches at the row level, and indeed the fields within the row won't be individually retrievable. That's why you apply that … interesting hack :) (i.e.$data['#markup'][$id]['#markup'] = $build[$id]['#markup'];
) If you'd instead render cache per field per row, then the problem goes away.Is there a reason you cannot do that?
P.S.: AFAICT you only replied to point 1, not to points 2/3. I'm particularly interested in your explanation for point 3. :)
This is where the "auto-placeholdering" mentioned in #2469431: BigPipe for auth users: first send+render the cheap parts of the page, then the expensive parts comes in: as a developer, you wouldn't have to deal with the burden of creating a
#post_render_cache
callback, generating a placeholder, etc. anymore. The Render API would do that for you. The Render API would see that you're using a "high-cardinality" cache context (i.e. "per user" caching causes many variations to be created — see #2469431-49: BigPipe for auth users: first send+render the cheap parts of the page, then the expensive parts and the IS there) and automatically determine do the placeholder/#post_render_cache
stuff for you.Comment #82
plach1: We cannot cache per-row markup because there is no row markup: to preserve the current rendering pipeline we need to store individual field markup, since most style templates use that to assemble the row output.
2: Will fix it
3:
$data['#markup']
is an array in that case due to the ugly hack explained above4: Sounds great, so we just need to ensure we always specify the most granular cache context
Comment #84
plachSorry, I misread your comment. We agreed to go this way before the first patch, my personal motivations were: reducing the number of cache entries and make sure we cache fields after they have gone through advanced rendering. Not sure whether Fabian or Daniel had additional motivations.
Comment #86
Wim LeersI see. Would be good to hear from Fabian & Daniel then :) I'd think #2453945: Use multiple get for #pre_render / #cache where possible would solve the I/O problem.
Comment #87
Fabianx CreditAttribution: Fabianx commentedYes, I missed that we only preserve #markup, not sure we should be stuffing an array in there. I usually mis-used [#attached]['my-extra-data'] for that :p.
- A cache get multiple is still slower than a single call, we also want to do a cache_get_multiple() of all fields for all rows in the end.
Also:
- To do this properly we need to add a #pre_render per views ->theme call, which is possible but it gets way more complicated then.
I would vote to make the 'hack' as clean as possible instead. Caching on the field level does not feel like the right granularity for me.
A review:
The adding of $cache_array is wrong here as that will have renderPlain() call the cache_set already.
'#keys' should not be set here.
I know I wrote that, but likely renderPlain() with how it is structured now is wrong.
renderPlain means:
render in isolation and do not update the stack, but in this case the meta data is never rendered to the stack later.
I thought the else block afterwards was always executed, in which case using $data = $render_cache->getCacheableRenderArray() is right.
Still thinking ...
Can use renderer->render() for now.
This is not needed and hte code can be simplified, you can just override $build['#markup'] or (mis-)use $build['#attached']['vrc-row-data'].
Sorry for the confusion ... still thinking of a good API to support that.
Comment #88
catchField level caching I'd be concerned about the number of sets as well (50 rows * 7 columns = 350 cache sets) so agreed that should be avoided.
Comment #89
plachThis should address #78.2 and #87. We still have a couple of issues, both caused by the fact that now we don't set cache keys in the render array on cache misses, to avoid storing cache items twice:
::createCacheableElement()
.We get only these CIDs:
while formerly we had:
Working on some additional coverage now, it would be good to have some feedback on this meanwhile.
Comment #90
dawehnerRegarding the unsetting which existed now and in previous versions ... Its fine to add it, given that $this->rendered_fields itself will have the stored values still.
That bit is a bit odd, can we at least explain why we are doing the
end()
call here?Why do we need this logic again? Note:
\Drupal\views\Plugin\views\query\Sql::getCacheTags
as part of that logic already, but it will be adapted in #2477157: rest_export Views display plugin does not set necessary cache metadata. I am not sure whether adding actual logic to that object is the best idea.Yeah especially this method on the ResultRow feels a bit icky, but we have helper methods which produce those values? Let's ask damiankloip for an opinion.
Comment #91
Wim LeersGreat catch!
Note that it must continue to live in
Renderer
where it does live today; it just should *also* be done inRenderCache
.Why not let
ResultRow
implementCacheableDependencyInterface
, then rename::getId()
to::getCacheKeys()
and merge the logic ofStylePluginBase:: getRowCacheKeys()
intoResultRow::getCacheKeys()
too?Then all this can be simplified to:
Probably I'm missing something though.
Comment #92
plach@dawehner, #90:
1: It's not only fine, it's required otherwise we get a logic exception because the system tries to process that data as
<head>
markup. Added a comment.2: It's the same in HEAD, added a comment here too. Actually I realized we can/need to cache also render tokens, because they rely on field handler data which is populated only when actually rendering field markup.
3: It's not the same logic as in HEAD: here we collect only the entities for a single row. We could certainly refactor it to allow it to be used by the
Sql
class.4: The reason why I moved this code on the
ResultRow
class is that we might need it to compute the full view CID in #2381277: Make Views use render caching and remove Views' own "output caching". This would allow us to avoid instantiating/depending on the style plugin for a logic that actually concerns only the row data. This looks like a way cleaner approach to me, actually.@Wim Leers, #91:
1: Thanks, done :)
2: Well, as explained above row IDs are a more generic concept than cache keys, although they are close. They can be used to compute cache keys for a single row cache entry, but also for the whole displayed result set, which should allow us to cache the full view. Well, it's not that simple, but this is the core idea.
I started writing some test coverage (not part of this yet), however it seems the most meaningful tests would involve link fields, which are broken currently (see #38). I spoke with @catch in IRC and we agreed that, if it's not a huge effort, we should try to fix them here, as we did for the contact link handler, to avoid introducing regressions.
Comment #93
plachComment #94
plachRe-uploading patch #92 for the bot, sorry for the noise.
Comment #95
dawehnerSure, but still, you know what I meant and what I did not meant :)
While getCacheTags is probably fine on there, I think the ID should potentially really be somewhere else, why, because its a detail for caching views rows IMHO,
who knows whether someone else comes up with some other idea in contrib. The style plugin at least now is swappable, but the row plugin by design is made as a simple object,
adding sort of logic onto it, removes that capability. You know, the cache plugin would be a great place for that, given that this method is certainly in the caching domain.
I'm curious whether we should fetch the information from the cache plugin instead?
How many plugins need it? Style and Field? which are kinda 20% or something like that, well, I guess being pragmatic here is okay.
That method name is a bit confusing, given that views already has a concept of preRender() but preRenderRow comes way after that, well
Its odd that there is no way to opt out of that row caching, is that really what we want?
quick style question, do we typehint against arrays in anonymous functions?
Comment #96
plachThis should address #95, except for:
4: Discussed this with Daniel: although normally we don't let people opt-out from render caching, since with Views it's very easy to expose sensible data, a possible solution could be to make the Tags cache plugin the default one, and make None skip render caching. I think we still need to clear up what's the relationship between render caching and cache plugins: at least for output there is potential for confusion, especially when we'll start working on #2381277: Make Views use render caching and remove Views' own "output caching".
5: I believe we cannot type-hint
use
d variables, they are actual variables not parameters.Comment #97
dawehnerOH you are absolutely right.
Comment #99
plach@Fabian, Wim:
I'm wondering whether it would make sense to move this method on the Renderer itself.
Comment #100
dawehnerTalked with @damiankloip and he agreed that putting stuff onto ResultRow just doesn't make sense.
Please always keep in mind that the code has to be sort of maintainable so putting cache related stuff into everything makes things worse.
Comment #102
plachThis fixes cache redirects as items were actually not picked form cache. Contact link access has been fixed to always return user cacheability for the access result, as suggested by Wim.
This also adds cacheable properties preservation, as suggested by Fabian, and starts fixing other field link handlers, as discussed with Daniel and Nat.
Comment #103
plachRe-uploading, bot seems to be stuck
Comment #104
dawehnerI'm curious whether we could not change those handlers here, given that they will be generalised in #2322949: Implement generic entity link view field handlers ?
I thought we don't need empty module files any longer.
Comment #105
plach1: Well, that would certainly make sense, but it would mean postponing this once again, unless we commit this and break HEAD.
2: It wasn't meant to be in the patch: they are part of another branch where I started to work on test coverage.
Comment #106
plachSpoke with @dawehner and @catch: this is now postponed on #2322949: Implement generic entity link view field handlers (besides #1867518: Leverage entityDisplay to provide fast rendering for fields), as it will make providing cacheability metadata for links way easier.
Comment #109
Fabianx CreditAttribution: Fabianx commented#1867518: Leverage entityDisplay to provide fast rendering for fields is in, so just one issue remaining.
Comment #110
plachRerolled this on top of #2322949: Implement generic entity link view field handlers. The test coverage added there in
FieldEntityLinkTest
is exactly the test coverage we were missing here, thus we should be good now.The only issue that might use some more discussion is the one exposed by the Contact link tests and outlined in #77 / #78 / #79. If that's a real concern and the fix introduced here on
ContactPageAccess
is correct, we need at very least a follow-up to make sure access cacheability is defined correctly everywhere.Comment #111
Fabianx CreditAttribution: Fabianx commented#2322949: Implement generic entity link view field handlers is in now!
Comment #112
plachRerolled.
Working on the Views counter field handler, which should be uncacheable (+ test coverage).
Comment #113
dawehnerWent through all of the field handers.
Here is a list of handlers which are potentially problematic:
\Drupal\system\Plugin\views\field\BulkForm::render
... isn't it a similar problem as the views counter one, ... we can't cache when the row index is there? This sounds like a problem we have to adress\Drupal\views\Plugin\views\field\LinkBase::render
into account? Isn't that part of the cacheablity metadata. Maybe there is magic involved I don't get yet.PS: Its pretty epic how many handlers we have been able to remove due to all this effort in the context of views <-> entity integration.
Yeah, container parameters! Can we add it to the top of the container.services.yml file?
Can we document what is in there? It would be also great to document why we have required_cache_contexts being configurable. That seems not obvious, to be honest.
So wait, can we document why we have this call here? It seems to be all we do is to fill out the default values of $build?
Not needed anymore
Comment #114
plachThese are leftovers of a previous version of #2322949: Implement generic entity link view field handlers.
Comment #115
catchWe have issues open already for the history markers - see [#2086767] and #2082319: Comment's node_new_comments View field history markers ("new" comment marker) forces render caching to be per user
Comment #116
Wim Leers+1
Yes, but this issue should then at least update
\Drupal\history\Plugin\views\field\HistoryUserTimestamp::render()
to set theuser
cache context.Great find, @dawehner!
Like @dawehner, this doesn't make any sense to me. It should effectively have no effect. Well, except for it adding
['#cache']['tags'] = []
.Woah, mind blown! :D
At the very least, this is missing documentation.
I don't understand the value of this. AFAICT the only value is to get the required cache contexts set on a render array.
But… then why not let
RenderCache::(get|set)()
do this? Because it'd mean additional function calls (thinkarray_diff( $required_cache_contexts, $contexts)
) and we don't want to incur those?IMHO this super advanced case — which will likely never be paralleled by anybody in contrib, and if so, affect 0.0001% of developers — should just retrieve the required cache contexts and apply it to the render array. It could then do it once, outside of this loop, and not have to call this method for every row.
These changes confuse me. I'll reroll to change it to how I think it should work, and see if that passes tests.
For example, if the first return statement is used, then we're caching that per user, until the contacted account entity changes and until the configuration changes. For no reason at all, because the anonymous user always remains the anonymous user.
In my
suggested-interdiff.txt
:AccessResult::forbidden()
. HEAD was varying this per permissions, which was indeed nonsensical.$access
variable that we keep reusing as we go further in the function body. It only needs to vary per user, just like HEAD was doing. The difference with HEAD is that we don't return "forbidden" — which is fine, because it *is* possible that a contrib module wants to allow a user to contact themselves to leave notes. If it's already forbidden, then such a contrib module would be impossible.$access
, but adds to it, and instead of manually saying "per permissions", we make it inherit the cacheability of$permission_access
.That's it. I also included
suggested-interdiff-against-HEAD.txt
to show the changes relative to HEAD.ContactLinkTest
andContactPersonalTest
still pass with these changes.Manually confirmed that this is necessary. The only reason it works in HEAD, is that if you modify your profile, changes to this are saved in tandem with changes to the
User
entity, which means that theUser
entity's cache tag is invalidated, and hence it all works.But, as you can see in
contact_user_profile_form_submit()
, no cache tag invalidation happens there.So it currently only works by accident. +1 to that issue this links to.
There are several problems here:
instanceof CacheableDependencyInterface
? :)CacheableDependencyInterface
must be considered to havemax-age = 0
, i.e. must be considered to be uncacheable. This is actually taken care of by::createFromObject()
already! So, no need for the if-statement.$access = $access->isAllowed()
being anything else but a security leak?If point one was incorrect, then I'm even more confused, because
::checkUrlAccess()
already promises to return anAccessResultInterface
object.AFAICT this can be simplified to:
What is "global markup"?
s/allow/allows/
Woah, this is getting all render tokens and caching them!
Not view display rendering, but view row rendering.
Unused. :)
Comment #117
Fabianx CreditAttribution: Fabianx for Acquia commentedLets not do this, this is a bug in the Renderer -> RenderCache split.
->set() should always merge in the required cache contexts before saving to the cache.
Renderer could instead only do it for $is_root_call = TRUE.
Both profit.
I don't think we want #cache properties, but an option to ->set().
If we want #cache properties, we btw. could use the normal #pre_render pattern again and would not need to use the RenderCache API.
#cache is for cacheability metadata, not for cache control of what to cache - that is at least Wim's and mine current understanding of it.
Should not be necessary as explained.
If you use that then you essentially still cache set twice and also attempt another cache get.
'keys' should not be set here.
Is there other data in $data[$id]?
I thought this would not be necessary?
RenderCache::set is responsible for merging default data.
Here the cache keys,tags,contexts need to be added to both original and $data, that is right for sure.
Comment #118
Fabianx CreditAttribution: Fabianx for Acquia commentedOpened #2483887: Mark RenderCache as internal for the bug, but this bug is not _blocking_ this issue as we found another approach here where we can get back to the nicer #pre_render pattern.
Comment #119
Wim Leers#117:
Then so should
::get()
, to avoid cache misses when it'd actually be a cache hit.Comment #120
Fabianx CreditAttribution: Fabianx for Acquia commentedOutcome of IRC was:
- We use #cache_properties for now to specify additional properties to cache (there is quite a few use cases independent of this when working on real sites, I encountered lots of them for render_cache in D7).
- We go back to the #pre_render + #cache + #cache_properties pattern, now that we can.
That will give use multiple cache get also for free (once renderer supports it) with a very little change in the future (two loops instead of one).
Comment #121
plachThis should address the reviews above, except for #116.9: I was assuming only field data would be displayed, so cache contexts would automatically fix that, but I realized there are also other tokens. Anyway, I will try to fix that together with uncacheable field handlers, I have an idea that might work, I'll try it later.
Please pay special care to the changes to Renderer/Render Cache, as I was not able to save an empty markup for the parent element without them. Feedback/suggestions welcome.
Comment #122
Wim Leers*Much* clearer, thank you! Now all that's missing for this, is test coverage.
Holy moly, this is night and day! Fifty times more understandable :D
Comment #123
Fabianx CreditAttribution: Fabianx for Acquia commentedThat is very clever. I like the thinking that if we have the children we don't need the '#markup' and it will save quite some space.
In case someone really needs the markup, they could use a #post_render callback to put it in another property.
If we support that as an API that also makes lots of sense.
No longer needed!
No longer needed!
Love how easy that now again is!
It turns out (sigh), that we could also support that manually by doing:
and then checking for CacheGet and rendering children manually in that case. (as before) ...
I defer to core committers however if #cache_properties is acceptable as I think its generally useful.
Comment #124
plachI also still need to check #116.4.
Comment #125
Wim LeersNW per #122, #123 and #124. Patch is green :)
Comment #126
plachThis takes care of #116.4, #122, and #123. While adding test coverage I found a glitch with cache properties handling: we were caching lots of additional properties for children, aside from
#markup
. Fixed that.@Fabian:
That's exactly what I originally did in one the patches above, but I sincerely doubt committers will argue against an addition that lets us simplify things like this.
Back to trying to make uncacheable stuff and render tokens work.
Comment #127
Wim LeersJust a few nitpicks. The test coverage looks great. This patch looks great. :)
s/children/children's/
Hrm,
empty($data['#markup']
could also mean!isset($data['#markup'])
?I think we want
isset($data['#markup']) && $data['#markup'] === ''
here, to remove all ambiguity.s/Check the/Check that the/
These 0s and 1s are actually FALSEs and TRUEs. I think plach went with integers instead of booleans because it'd yield a more legible end result? :)
Comment #128
plach2: Great point, although I think we don't need to care about notices, on the contrary, so I simplified the check to
$data['#markup'] === ''
.4: Exactly :)
Still working on the rest...
Comment #129
Fabianx CreditAttribution: Fabianx for Acquia commentedPatch looks really great now!
Comment #130
dawehnerIt would be nice to explain why its okay to mark them as safe.
Comment #131
jibranNice work @plach
This is all magic to me. :(
Can we please add some inline commenting here?
Now these are some good docs. :)
Comment #132
plachWell, they were originally marked as safe and their parent (which is just a concatenation of their markup) is marked as safe so they must be too :)
I guess we need Fabian or Wim for a better answer :)
Can you specify where you'd wish to have more comments and what they should clarify? I mean, there are already a couple of inline comments in the code block you outlined :)
Comment #133
jibranHow about this? This is my understanding of the code I might be wrong. Please feel free to correct it. :)
Comment #134
plachThis should fix row render tokens handling and uncacheable handlers. I discussed with Daniel the introduction of a
::postRender()
method to field handlers, given that#post_render
does not work, since its results is cached, and#post_render_cache
does not work either, as it's impossible to reconstruct the view context without re-executing it.If this approach is ok, we just need to update the field handlers mentioned in #113 and add some test coverage and we should be done.
This also incorporates #133 with a few adjustments to avoid reverting a previous review.
Comment #136
plachTest fixes + clean-up.
Comment #138
plachWrong patch, interdiff in #136 is correct.
Comment #139
Wim LeersOnly nits. IMHO this is RTBC. But it'd be great if @dawehner could review the #134 + #136 interdiffs — I don't know "Views tokens" well enough to be able to properly judge that.
Great comment, but to address #131, maybe append
s/fields/field/
Nit: s/Definition of Drupal/Contains \Drupal/
Comment #140
dawehnerAny reason for this change?
It feels like this need some documentation? I don't understand it either, to be honest.
Do you mind renaming it to
getFieldTokenPlaceholder
... contains
should we maybe replace it with
$style->getField()
Comment #141
Wim LeersComment #142
plachRerolled and fixed #139 and #140. Working on additional test coverage and then we should be done for reals :)
I was just reverting a previous unneeded change.
It's a unit unit test :)
Comment #143
dawehnerOnce we have the complex test example of plach, we should be pretty much done with it.
Comment #144
Wim Leers+1!
Excellent work, plach!
Comment #145
plachNow with additional test coverage
Comment #147
dawehnerShould we use name the view test_render_row_cache maybe? Maybe that is more clear then, what its about.
Nitpick: public ...
I'm curious whether it works on subdir installlations.
we don't put uuids in test views.
Comment #148
Fabianx CreditAttribution: Fabianx for Acquia commentedTwo remarks from live-review yesterday:
All these functions need to be moved to the interface, because views depends on them being there.
Else using a custom cache plugin that is not extending CachePluginBase will fail completely and in strange ways ...
If we don't want to muddy the interface, we would need to check for the functions directly or for another interface and skip caching if that interface / function is not present.
I think it would be good to explain a little more that its not for totally uncacheable data, but for data that is uncacheable / dynamic per row, but cacheable for the complete page (or complete view) in upper layers.
Comment #149
plach.
Comment #150
plachThis should address #147 , #148, fix test failures and improve test coverage.
Investigating the field handlers list in #113 to see whether there's anything left to do.
Comment #151
plachThis fixes the
BulkForm
handler mentioned in #113.ContextualLinks
could not be fixed as they are broken in HEAD (see #2487742: Contextual links Views field handler is broken).HistoryUserTimestamp
has its own issue to fix the View field handler (I cannot find it right now).Additionally operation links are still not working, as they return no cacheability metadata. The admin content view is working though thanks to a favorable combination of things. So, we can either commit this or postpone it to #2473873: Views entity operations lack cacheability support, resulting in incorrect dropbuttons (and make that one critical), but we should be done here.
Comment #152
plachComment #153
dawehnerLet's get it in!
Comment #154
Wim LeersThis is what Daniel meant to do :)
Comment #155
plachThe user history issue: #2082319: Comment's node_new_comments View field history markers ("new" comment marker) forces render caching to be per user
Comment #157
catchI double checked this, and there's no interface that CachePluginBase implements - it just extends PluginBase. So yes, but that's a pre-existing problem with views plugins.
I've been following this off and on, and the patch has got much, much more readable in the past week or so. Really nice work getting it to this point.
Committed/pushed to 8.0.x, thanks!
Comment #158
plachAwesome, thanks!
Comment #159
alexpott