Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Updated: Comment #N
Problem/Motivation
Emitting menu links will soon require a full route object to check for access.
This requires single loading of route objects all over the place, which is potentially a DB query at the moment.
Proposed resolution
This patch efficiently preloads non-admin routes in order to speed up menu link display.
Remaining tasks
User interface changes
API changes
Original report by @dawehner
Comment | File | Size | Author |
---|---|---|---|
#59 | interdiff.txt | 1.97 KB | dawehner |
#59 | route_rebuild-2058845-59.patch | 11.43 KB | dawehner |
#56 | interdiff.txt | 2.34 KB | dawehner |
#56 | route_rebuild-2058845-56.patch | 11.16 KB | dawehner |
#44 | route_rebuild-2058845.patch | 11.37 KB | dawehner |
Comments
Comment #1
catchThere's been lots of discussion of this in person/irc, but this might be the only issue.
I think we definitely need to look at pre-loading/caching the most commonly accessed routes, retitling and bumping priority.
Breadcrumbs create some inbound route lookup that we've not had so much before.
Comment #2
dawehnerThis loads the routes on breadcrumbs.
Comment #4
dawehnerMissing services change.
Comment #6
catchMore profiling done on just how bad the route generator is at the moment: #2102777: Allow theme_links to use routes as well as href.
Comment #7
dawehner#4: breadcrumb-2058845-4.patch queued for re-testing.
Comment #9
star-szrTagging for reroll.
Comment #10
dawehnerThis tries to implement the /admin logic in a preloader.
Comment #11
tim.plunkettShouldn't that be === FALSE? Otherwise "/admin/structure/views/view/admin_view_custom" would match.
Comment #12
dawehnerYeah right.
Comment #13
Crell CreditAttribution: Crell commentedDon't you mean non-admin?
Er, it's non-admin routes that we're pre-loading. Right?
Comment #14
star-szrComment #15
dawehnerThere we go, this time also with a proper test.
Comment #16
dawehnerThere we go, this time also with a proper test.
Comment #17
Crell CreditAttribution: Crell commentedDrupal.org fail again. Which of those should we review? :-)
Comment #18
dawehnerLet me quote my UI diff tool:
The files are identical.
Comment #19
moshe weitzman CreditAttribution: moshe weitzman commentedCode great to me. I'd love a benchmark so we know how much this is helping (or hurting). After all, we are doing a lot of route loading. Perhaps we need to update menu link access checking before thats possible? Should we postpone for that?
Could we restrict more? What about partial HTML requests like a single block? Can we identify those versus full page requests?
Comment #20
moshe weitzman CreditAttribution: moshe weitzman commentedComment #21
dawehnerI would love to, though I am not aware of a way yet, as we don't have for example single block requests yet.
Comment #22
Crell CreditAttribution: Crell commentedWhy does this need to be saved to the object as well as the state system? A route rebuild is almost always followed immediately by a redirect, which would throw that away anyway. It seems unnecessary.
I'd prefer to punt on partial requests until we know for sure how to identify them. This entire issue is non-API-changing, just performance optimization, so we can refine it at our leisure as we decide what qualifies as "good enough" performance.
Speaking of, what's the best way to benchmark this? It's something that would affect the whole request so my knee-jerk thought is ab, but Mark yells at me every time I try to benchmark anything without xhprof. :-)
Comment #23
catchxhprof for the whole request works great.
Comment #24
dawehner16: routes-2058845.patch queued for re-testing.
Comment #26
dawehner16: routes-2058845.patch queued for re-testing.
Comment #28
tstoecklerPatch looks great. I only have minor points.
Load -> Loads
perhaps "the" can be dropped.
Event -> event
html -> HTML
More importantly: What does this have to do with menu links? It seems that would be important information to document more explicitly.
Here and elsewhere: I'm wondering whether there isn't a better word for "non-admin", i.e. a positive word. I could come up with anything, but non-admin is certainly suboptimal.
non admin -> non-admin
It seems strange to say "_content routes". It's sort of obvious what you mean, but _content is not an actual word :-)
See above regarding "menu links and co."
Because of the array_unique() I don't think anything would actually go wrong currently, but I think we should listen to the REBUILD_FINISHED event and reset $this->nonAdminRoutesOnRebuild. This is useful if route rebuilding happens multiple times per request.
Any reason for the -1024? If so, a comment would be great.
Maybe: This needs to run before HtmlViewSubscriber.
Also: *Why* that is the case would also be important to document. That's not saying much but to me, at the very least, that's not obvious.
html -> HTML
Comment #29
dawehnerThank you for your review, great points!
The patch adds some basic description of the idea onto the class documentation. Does that help?
Great idea. It is always great to see that new features can be used in more places than the itinitial usecase.
I totally agree that we should always document why a specific priority is chosen. Here we can just drop the line of documentation as previous versions of the patch just moved it from VIEW to REQUEST as menu links could also be rendered on the actual controller, so using the VIEW event is too late.
Comment #32
dawehnerSadly I totally misunderstood your suggestion with the used event.
Comment #33
dawehnermissing attachment.
Comment #35
dawehnerFixed also the unit test
Comment #36
pwolanin CreditAttribution: pwolanin commentedGood idea, but I really don't like depending on the path for this (or anything in D8) - can we flag non-admin routes when they are defined?
We shouldn't ship with this fragile path matching implementation - though it might be ok to go in if we have a critical follow-up issue to define something better.
For example - we could add to the pre-loaded list all routes accessed by users with a certain role.
Comment #37
msonnabaum CreditAttribution: msonnabaum commentedTo test this, I used a node page viewed as anonymous user, after giving anonymous enough perms to produce a few more links.
As you can see, the number of queries made by the router is cut in half:
7 is still kind of a lot, but this is certainly an improvement. I'd be ok with this going in and then we can continue to tune it as we discover more.
Also, we should rename this class so that it doesn't have the implementation details in the name. Just "RoutePreloader" would be fine.
Comment #38
msonnabaum CreditAttribution: msonnabaum commentedLet's try this image again:
Comment #39
catchLooks like a good improvement. Do you know which routes the 7 remaining queries are for?
Comment #40
tim.plunkettCross posting #1316692: Convert hook_admin_paths() into declarative properties on routes
Comment #41
sunDo we need this conditional multi-layer loading for any particular reason or could we merge the whole logic into getNonAdminRoutes?
Also, as @msonnabaum already mentioned, I'd additionally leave the "non-admin" detail out of the method names — it's perfectly possible that we want to preload further routes, so just getRoutes() would be sufficient?
Comment #42
msonnabaum CreditAttribution: msonnabaum commentedTurns out the remaining 7 were routes like user.logout that don't have _content, so we were excluding them. I don't really see why we'd want to, so I removed that check and now it's down to 2 queries from 14.
This patch also renames the class, although I didnt rename the methods because they are private. I had the same thought about getNonAdminRoutes, so I removed that and the property that caches those, since onRequest should only get called once anyways.
Comment #43
dawehnerMy reason for checking for _content was to not load routes like the REST ones, generic AJAX controllers etc. I think checking for _content and or _form, _entity_list, _entity_form or _entity_view should cover all we have in core.
Comment #44
dawehnerI totally like the new naming, let's go with it.
On the other hand I am not convinced of the blacklisting approach instead of the whitelisting we have at the moment. This adds back _content
but expands it with _form, _entity_form, _entity_view and last allow to define routes to be preloaded.
Comment #45
tim.plunkettThat won't help user.logout, which is _controller (it returns a redirect).
Comment #47
msonnabaum CreditAttribution: msonnabaum commentedThink this patch is what you meant.
That said, I really really dislike this:
This will result in additional queries because contrib modules will forget to add this option, and they will only be found by people who are profiling, which is very few. A typical module author should never have to worry about route preloading.
Also, I'm not crazy about arguing that we need to whitelist without any data about memory usage to show that it's worthwhile at all.
Comment #49
dawehnerHere is a really simple controller:
This was executed on the standard profile, so if you install contrib modules the amount
of routes would easily get at least times 2 or 3.
The result was
so this feature would add 4 MB of ram to every request, in actual real world scenarios over 10 MB.
Comment #50
catchJust checked two 7.x sites - both are relatively complex, but don't have a ridiculous number of contrib modules enabled.
120 modules | 120+ non admin/% routes | 600+ admin routes.
150 modules | 180+ non admin/% routes | 700+ admin/% routes.
The most modules I've ever seen installed on a site was over 300, let's say 300 non-admin routes for that one, don't have that db handy.
Not sure what that gives us, but I' think we need to be reasonably OK up to 150-200 total non-admin routes, past that you have other problems to worry about.
If this was 7.x, we'd very easily be able to see which routes had menu links pointing to them, vs. MENU_CALLBACK which is nearly always going to be inbound rather than outbound.
I don't see a way to get that information without introducing a dependency on the menu link system or using CacheCollector to build the list though.
Comment #51
msonnabaum CreditAttribution: msonnabaum commentedI don't quite get what #49 is showing. Don't you just want to measure memory usage between the number of routes passed to getRoutesByNames?
Comment #52
msonnabaum CreditAttribution: msonnabaum commentedTalked to catch and dawehner in IRC about this and we came to the conclusion that we should just go with the simplest version in #42, then we can open a followup to optimize memory usage by switching it to something like the CacheCollector pattern.
Attached is the same as #42.
Comment #53
moshe weitzman CreditAttribution: moshe weitzman commentedWe have green, and a consensus. RTBC
Comment #54
moshe weitzman CreditAttribution: moshe weitzman commented52: route_rebuild-2058845-52.patch queued for re-testing.
Comment #55
alexpott#11 is wrong
Should be
Afaics
Also noticed
Unused use
Comment #56
dawehnerGood catch!
Comment #57
sunThanks!
Comment #58
alexpott@dawehner I suggested
For a reason... what if you have a site that is aimed some type or types of administrators and you have views on paths like /administrator/typeA and /administrator/typeB or whatever.
Comment #59
dawehnerOkay.
Comment #60
Anonymous (not verified) CreditAttribution: Anonymous commentedi love this patch. preloading++
having said that, i wonder if we should make RoutePreloader just mechanism, and allow the policy to live in event subscribers to a 'route.preload.names' event or similar.
core would ship a listener that implemented the non-admin route preloading thing, and contrib can go nuts. like config object preloading, i think we should take care to avoid one-size-fits-all, and make sure core doesn't get in the way. leaving as needs review because i'm late to the party, and i can live with this coming in a follow up.
if we do make this a follow up, then we should rename RoutePreloader to NonAdminRoutePreloader or something, to make it clear we've put the policy as well as the mechanism in this class.
Comment #61
dawehnerWhile I totally appreciate the idea I struggle really how we want to implement it. You certainly need somehow access to the available routes during this event, so should we pass the full route collection onto this custom event.
Alternative all the event subscribers could use both a routing event and the preloading event and use both to set the names properly.
Comment #62
Wim Leers59: route_rebuild-2058845-59.patch queued for re-testing.
Comment #63
Anonymous (not verified) CreditAttribution: Anonymous commentedmy comments weren't meant to block this going in as is. we can address my point in a follow up.
Comment #64
dawehnerGreat, thank you!
Given how simple the actual code is and given how few people will actually tweak this service I really think it would be fine for them to replace the full class as it is.
Comment #65
Crell CreditAttribution: Crell commentedI agree, this is an entirely optional optimization with no API impact. We or contrib and rip it out and rewrite it whenever we feel like it without breaking anything; it certainly doesn't need an API of its own. Let's land it.
Comment #66
alexpottCommitted 3cd6aa8 and pushed to 8.x. Thanks!