Problem/Motivation
blocks #2450897: Cache Field views row output
this issue is kinda blocked on: #2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption
Anything that caches renderable arrays to be used by the render pipeline needs to properly support all bubbleable metadata (cache contexts/tags/max-age + assets), plus #markup
plus #cache_redirect
for bubbled cache contexts.
Therefore it MUST use a dedicated render cache API. If anything that deals with render arrays, uses $cache_backend->(get|set)
directly (even with the $renderer->getCacheableRenderArray()
call), then we are still in trouble as #cache_redirect
would need to be re-implemented - otherwise a bubbled 'moon' context will be missing and the output won't be varied by 'moon' as necessary. The Views output cache suffers currently from that.
Anything that uses cacheable render arrays and wants to cache that MUST go through the Render (Cache) API and not use $cache_backend->(get|set)
directly.
It must provide the original #cache
data too so that what is currently $pre_bubbling_cid
works correctly.
For most cases in core the #pre_render / #cache pattern is used to mitigate this effect, however there are use cases where that pattern is not enough, e.g.
- Multiple Cache-Get / Prepare and render remainder
A number of rows can be prepared in fully, but only non-cached rows, should be prepared and rendered.
The #pre_render / #cache pattern is not enough here as they don't have any multiple support, but assume each row is rendered in a single way.
- Something like views that according to its internal architecture can't use the #pre_render / #cache pattern (e.g. because it supports its own cache layer).
This can also use the new renderCache API and has to deal less with the interna then.
Proposed resolution
An ideal API would decouple the render caching implementation and allow to do:
$cached_rows = $this->renderCache->getMultiple($render_cache_array_keyed_by_row_id);
// ...
foreach ($rendered_rows as $row_id => &$rendered_row) {
$this->renderCache->set($rendered_row, $render_cache_array_keyed_by_row_id[$row_id]);
}
return $cached_rows + $rendered_rows;
A render_cache array is a render array with just the #cache, #attached and #markup property set.
This relates to the other issue of using multiple cache_get in the renderer that it will make it simpler and we can also optimize some other cases like BlockViewBuilder, etc. more in an easier way.
However using the render cache API is a MUST for modules dealing with render arrays - else it will break the caching.
This should also remove Renderer::getCacheableRenderArray() as that gave a wrong promise of solving the cacheSet/Get and using $renderCache is simpler than the $cache anyway.
Before:
$original_build = $build;
$renderer->render($build);
$data = $renderer->getCacheableRenderArray();
$cid = $this->computeACidUsingContextsAndKeys($data);
$this->cache->set($cid, $data);
and even with that much work the result is incorrect when something bubbles up and createCid($original_build) != createCid($build).
After:
$original_build = $build;
$renderer->render($build);
$this->renderCache->set($build, $original_build);
The original is needed to compute the pre-bubbling CID.
We cannot kill getCacheableRenderArray(), yet, because core needs to use it in HtmlRenderer::prepare(), which is a very very special case.
--
Thoughts: However $renderer->render($build); will already render cache the data, also it is unclear what the stack behaves like and what one would return.
The simplest API probably would be to use:
$this->renderCache->renderAndSet($build);
return $build;
With the understanding that $build afterwards can be used as if it was coming from cache. This is also very important for any placeholdering work we do.
This is different from calling ->render that $build would be replaced with the cacheableRenderArray() AND that no data would be bubbled on the stack, which it should not be.
Remaining tasks
None.
User interface changes
None.
API changes
RendererInterface::getCacheableRenderArray()
removed; now at RenderCacheInterface::getCacheableRenderArray()
instead. See #28 for rationale.
Beta phase evaluation
Issue category | Task, because not a bug, but refactoring to prevent significant amounts of duplication for #2450897: Cache Field views row output. |
---|---|
Issue priority | Major because blocking a critical, and because it enhances the maintainability of Renderer significantly. |
Prioritized changes | The main goal of this issue is performance: it blocks #2450897: Cache Field views row output. |
Disruption | There is only one API change, it's a very, very rarely needed API. Two calls to it in core. Very likely zero in contrib/custom code. So, effective disruption is approximately zero. |
Comment | File | Size | Author |
---|---|---|---|
#37 | decouple_cache-2466585-37.patch | 47.57 KB | Wim Leers |
Comments
Comment #1
catchThis makes sense to me to use for multiple get/set. #pre_render just doesn't work at all for that.
Comment #2
Fabianx CreditAttribution: Fabianx for Acquia commentedRemoved page_cache example as its controversial.
Comment #3
Wim LeersComment #4
Fabianx CreditAttribution: Fabianx commentedComment #5
Wim LeersFirst, adding formatting/structure to the IS, to make it more parseable. Then going to analyze it.
Comment #6
Fabianx CreditAttribution: Fabianx for Acquia commented.
Comment #7
Fabianx CreditAttribution: Fabianx for Acquia commented.
Comment #8
Fabianx CreditAttribution: Fabianx for Acquia commentedWe could also just expose the cache parts of the renderer as public API, but I think that makes the renderer too complex ...
And I am pretty sure this kinda blocks #2450897: Cache Field views row output.
While I was able to re-implement a ton of APIs in that issue, having access to cacheGet / cacheSet will make it _way_ simpler and cleaner ...
But this issue is kinda blocked on:
#2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption
Because having to specify $pre_bubbling_cid without means to create $pre_bubbling_cid is problematic and I think we don't want to expose createCacheId() as public ...
Comment #9
YesCT CreditAttribution: YesCT commentedComment #10
Fabianx CreditAttribution: Fabianx for Acquia commentedTagging for Dev Days
Comment #11
plach#2469277: Changing #cache keys during #pre_render or anywhere else leads to cache redirect corruption has been committed!
Comment #12
plachComment #13
Fabianx CreditAttribution: Fabianx for Acquia commentedLet me get a quick patch uploaded for that.
Comment #14
Fabianx CreditAttribution: Fabianx for Acquia commentedQuick first WIP :
- Test fixes for unit tests are still wrong. (just made them pass for now)
- BC for getCacheableRenderArray needs discussion, but likely should stay on renderer as BC
Overall works nicely though.
Comment #15
Wim LeersJust an idea, but… I think that in an ideal world, we wouldn't have a
RenderCache
service that is used directly by theRenderer
service, but we'd instead have a service decorating theRenderer
service, and in that way adding support for render caching.That'd mean
Renderer
would only have to care about rendering itself, not about render caching.$cacheContextsManager
More complete review to follow.
Comment #16
Fabianx CreditAttribution: Fabianx for Acquia commented#15
1. That would double the function calls to doRender(), not sure we want to afford that - it is very much our critical path and the caching part is already quite isolated ...
And in an ideal world we would have a CachedRendererDecorator, which uses the renderCache service as composition > inheritance / decoration, but I don't see that its worth it ...
2. Will fix, was straight c&p (or move & paste) from Renderer (which means core missed one rename ;)).
Comment #17
catchHaven't reviewed in depth yet but general +1 to splitting this out, and what I did review was encouraging.
Comment #18
plachJust a quick look to get some familiarity with this code.
Would it make sense to have a dedicated method for this?
These need to be updated.
This could be a great place to add some documentation about the key concepts of render caching: keys, contexts, tags and so on. Sorry, if they are already documented in a better place :)
Is there any value in assuming we are caching HTML markup?
This paragraph is a bit confusing: it's not clear why Drupal would need additional metadata. As a consequence of a code change? Or new code installed? Maybe an example would help.
80 chars exceeded, what about:
"Gets the cached, pre-rendered element of a renderable element from cache."
Would it make sense to specify only pre-bubbling cacheability metadata then? I guess it would make the contract clearer.
PHP doc mess :)
Comment #19
Wim Leers#18.3: already documented at https://api.drupal.org/api/drupal/core%21modules%21system%21theme.api.ph... and https://www.drupal.org/developing/api/8/render/arrays/cacheability :)
Comment #20
plachThanks, a couple of
@see
would be great, then :)Comment #21
Wim LeersI'm going to take a stab at addressing all feedback.
Comment #22
Wim Leers#16:
#18:
Renderer
is already HTML-specific anyway. Many concepts, and every template.RendererInterface::getCacheableRenderArray()
. Also not sure how to improve without making it super wordy.Review:
I'd argue these should just be
get()
andset()
.The "cache" bit is implied by the service name.
Comment #23
Fabianx CreditAttribution: Fabianx for Acquia commented#22: Yes, just get and set. :)
Comment #24
plachI was suggesting to put this on the interface, looks fine to me otherwise.
Comment #25
Fabianx CreditAttribution: Fabianx for Acquia commentedYep, lets put that on the interface as well.
If we rename, need to rename here, too.
Not much found in review.
Things to address:
- a) Above points
- b) Rename cacheGet / cacheSet to just get / set. (oversight on my part)
- c) Discuss what to do with the unit tests:
Currently we couple renderer and renderCache together and test them together. That works, but is not how unit tests are usually structured ...
We could put c) to a follow-up if everyone agrees that the test coverage "purification" should not be blocking going ahead with Views-Row-Caching (critical).
Comment #26
Wim LeersFixed #22's bottom remark, thanks to the +1 in #23.
Fixed #24.
Fixed #25.
RE: unit testing
RenderCache
(#25.c)): I think that these are inherently tightly coupled. I think our current test coverage is sufficient.Another review.
createCacheID()
is a protected method. But at #2450897-23: Cache Field views row output.1, it is said that part of the reason we're doing this issue, is to allow other code to call this method (to avoid duplication). So… do we still want to make that method public, or…?Comment #27
Fabianx CreditAttribution: Fabianx for Acquia commented#26
1. If there is a cacheSet / cacheGet method the createCacheID does not need to be public.
2. Agree, it is independently useful.
If c) unit testing is acceptable, then all we need to do is IMHO to check again on the docs for getCacheableMetadata in the Interface of the renderer and we should be done here.
Comment #28
Wim Leers#27.2: LOL, why didn't I think of that? :)
You meant
::getCacheableRenderArray()
, but yes, I'd agree that that is all that's left then.RenderCache
.HtmlRenderer
, with a very very exceptional edge case, 3) Views'CachePluginBase
, which will go away as part of #2381277: Make Views use render caching and remove Views' own "output caching". So disruption of moving it toRenderCache
would be very very minimal.So I say: let's keep it only on
RenderCache
. Did that in this reroll. Up to you guys to judge now.Comment #30
Wim LeersDammit, both #26 and #28 contain an accidental change to
UserData
, causing those test failures.Comment #32
Fabianx CreditAttribution: Fabianx for Acquia commentedRTBC on all interdiffs since my original implementation.
Need someone else for final sign-off now, but in my opinion its ready now.
--
We still need a beta eval and added the API change tag for the getCacheableRenderArray() move.
Comment #33
Wim LeersAdded beta evaluation. Updated the remaining tasks/UI changes/API changes sections too.
Comment #35
Wim LeersForgot to update one bit.
Comment #36
plachIt would be great if we could provide also a
::getMultiple()
method, this should allow to make things even faster in #2450897: Cache Field views row output.Why is this pointing to itself?
I thought we were going to rename this to
set
, weren't we?Comment #37
Wim LeersSorry for those bad rerolls :(
Comment #38
plachWim and Fabian are happy with this, so who am I to make them sad?
Comment #39
plachComment #40
plachThis is actually blocking a critical
Comment #41
plachI went ahead and applied #37 in #2450897-74: Cache Field views row output, but I have some troubles using the new API and make things work. I had to perform some adjustments. Please check whether I'm using it the intended way or I'm doing something wrong before setting this back to RTBC.
Comment #42
Fabianx CreditAttribution: Fabianx for Acquia commentedI am setting back to RTBC as I provided feedback in the other issue and this is also without that other issue useful in itself (albeit not critical) and also blocks BigPipe work.
Comment #43
alexpottCommitting this to unblock further work. We can still make any tweaks necessary if #2450897: Cache Field views row output finds it to be necessary. Committed 83c5370 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Fixed unused use statements on commit.
Comment #45
fgmI would say this needs a change record : using the cache API directly will be the obvious choice for many.
Comment #46
Wim Leers@fgm: Except that only in the extremest of extreme edge cases, one would even be tempted to implement their own render caching. 99.9% of code should just use render caching, which already is thoroughly documented: https://www.drupal.org/developing/api/8/render/arrays/cacheability. Otherwise, I would agree.
Comment #47
fgmMakes sense : I added a note about this on the Cache API intro at https://www.drupal.org/developing/api/8/cache so that devs looking at the Cache API can be made aware of this.
Comment #48
Wim LeersGood call! :) Tried to clarify it further.