Problem/Motivation
Currently, the menu tree parameters are cached per menu, per route match at \Drupal\Core\Menu\MenuLinkTree::getCurrentRouteMenuTreeParameters().
This means that:
- we do a cache get per menu that gets rendered
- but we don't ever call that method when the menu block is render cached
- and the menu block is currently already render cached (despite that caching actually being wrong/broken, but that's out of scope; when #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees lands, menu blocks will still be render cached, so this analysis remains valid)
But, worse:
- to retrieve render cached menu blocks, we need to calculate cache IDs
- the cache IDs depend on active menu trails
- active menu trails are currently not cached at all
- which means that if we have N menus on a page, we also perform N queries
- (Standard profile has 4:
account, main, tools and footer)
Proposed resolution
It'd be better to remove the barely useful caching in MenuLinkTree::getCurrentRouteMenuTreeParameters(), because that is only used on cache misses, and instead apply caching to the active trails, which happens is calculated for every menu on the page.
That replaces N DB queries with a single cache get.
Remaining tasks
Review.
User interface changes
None.
API changes
None.
Comments
Comment #1
wim leersBenchmarking
Always 1000 requests.
Front page, UID 2
Front page, UID 1
Profiling
Function calls, UID 1, frontpage
Comment #3
wim leersI forgot to attach the histograms I made.
Comment #4
wim leers#1 came directly from old work on #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees that had been lingering around. I just dusted it off and got it to work again. I should've run tests too, obviously.
I fixed this by adopting the Cache Collector pattern. Performance numbers are the same. Just 8 function calls less.
Comment #5
dawehnerQuick fix of the issue summary to not contain the same picture twice
Comment #6
wim leersOops, didn't mean to put the images in the IS at all! Dreditor--.
Comment #7
catchNoticed today that apart from actual router matching, this is the only non-cache, non-state database query we execute in the minimal profile on every request.
So it represents potentially a 50% reduction in queries to the actual database when using different cache/state backends.
Comment #8
dawehnerI'm curious, how often do we things practically store per URL already?
Should we document that CID will be calculated in getCid() ?
I like that a lot that this is all still inside MenuActiveTrail, so in case someone decided to build a trail, for example based upon history, it would be possible to do so.
Great change!
I'm curious whether we should adapt the unit test to test the new caching code, it would be possible, this is for sure.
Comment #9
wim leersIn this case, we already were caching similar data per URL, but now we're just being smarter about that. See the IS for details. :)
No, because this is a pattern that you see elsewhere too, e.g. in
\Drupal\Core\Asset\LibraryDiscoveryCollector.Comment #10
catchPath alias pre-load cache is also per-URL.
Comment #11
dawehnerWell, I'd kinda like to save us from breaking the following piece of code for example:
Nothing documents why the ksort() is needed, yes I know, but maybe someone else doesn't know in the future. Having test coverage for that helps against that.
Comment #13
dawehnerWell, then lets do it. I think the patch itself is sane
Comment #14
wim leersI approve of this test!
Thanks for the test coverage!
(I was thinking of using the 'route' cache context instead; but that'd have required me to inject
CacheContextsManagerhere, which would've been ugly. This is better.)Comment #15
fabianx commentedRTBC, looks great now with the added test coverage.
Train of thought
I pondered this change for a moment, but because blocks are plugins it is pretty unlikely that someone will display a menu manually without going through the block (or some other #pre_render cache).
[ Also the code to display a menu manually is really complicated and placing a block is really simple. ]
Comment #16
catchThis looks great. I also saw these queries when profiling with the minimal profile/front page/logged out - they're the only uncached database queries apart from path alias and routing lookups. So this will both reduce the number of queries and it also cuts out overhead from SelectQuery.
Committed/pushed to 8.0.x, thanks!