Ran into this issue while performance troubleshooting.
Line 795 of menu.inc has the following:
794 // menu_tree_check_access() may set this ahead of time for links to nodes.
795 if (!isset($item['access'])) {
796 if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
797 // An error occurred loading an object.
798 $item['access'] = FALSE;
799 return FALSE;
800 }
801 _menu_check_access($item, $map);
802 }
The if statement in line 795 makes the assumption that if $item['access'] is not set, then the menu object is not loaded, which then makes a call to _menu_load_objects, which can be a very expensive call if the menu item in question happens to be a view. Oftentimes the objects are already loaded and stored in the menu cache.
The reason the menu item object is loaded at this point (as far as I can tell) is to ensure that $item['access_callback'] is properly set so that the call to _menu_check_access at line 801 is able to proceed.
I am submitting a patch that corrects this behavior by only loading the objects if $item['callback_access'] is not set. If you have menu items that refer to views (or any other expensive load_functions for that matter), this patch will dramatically increase your sites performance.
For more information check here: http://drupal.org/node/402944#comment-2762506
Comment | File | Size | Author |
---|---|---|---|
#25 | drupal-_menu_link_translate-753064-25-D6.patch | 849 bytes | mikeytown2 |
#20 | drupal-_menu_link_translate-753064-20-D6.patch | 2.23 KB | mikeytown2 |
#17 | 753064_avoid_calling_menu_load_objects-D6-1.patch | 1.03 KB | dixon_ |
#16 | 753064_avoid_calling_menu_load_objects.patch | 1.04 KB | apaderno |
#13 | drupal-753064-d6.patch | 2.33 KB | mikeytown2 |
Comments
Comment #1
MixologicSo I guess if you attach a patch, and then preview, your patch doesnt stick? here it is again.
Comment #2
dawehnerCorrect status
Comment #3
Damien Tournoud CreditAttribution: Damien Tournoud commentedPlease see #638070: Router loaders causing a lot of database hits for access checks, for a lengthy discussion on the same subject.
Comment #4
MixologicI read that thread and didnt see anything that addresses what this patch is addressing. Closely related, but not this at all.
There is a logical fallacy in menu.inc that assumes if we do not know the users access, then we *also* do not know the callback function to determine that access, and must call the load_function just to find out what that access callback is.
So unless there is some reasonable likelyhood that the cached version of the menu contains a different callback than what we're going to see by calling the load_function, Im pretty sure this is necessary, and will reduce a lot of unnecessary load_function calls.
Please give it a closer review.
Comment #5
Damien Tournoud CreditAttribution: Damien Tournoud commentedThe test results speak by themselves. At the very minimum:
is_numeric($item['access_callback'])
instead of!isset($item['access_callback'])
... which doesn't really make any sense to me_menu_item_localize()
because the objects can be used when there is a title callbackComment #6
Damien Tournoud CreditAttribution: Damien Tournoud commentedRetitling. We can avoid calling
_menu_load_objects()
at all when translating a menu link in one specific case:This is not much, but might be significant in some use cases (ie. Views) when costly objects are loaded. This specific change could be backported to D6.
Alternatively, we could refine
_menu_load_objects()
to load only the objects we care about for a particular callback. This might get us a significant improvement in performance because for links we only care about the access and title callbacks, that might in a lot of cases not need to load the objects (or not all of them). I believe that this is a more significant change, that can only targets D7 (or even only D8 at this stage of the game).Comment #7
catch@Damien: I haven't verified it, but I think Mixologic's original report on Views mentioned _menu_load_objects() getting called twice here when viewing the actual views page callback. Views doesn't static cache it's loader function so that results in it building everything twice.
Comment #8
indytechcook CreditAttribution: indytechcook commentedIs this a worry if _menu_load_objects are cached? #238695: Document static cache requirements for menu loaders
Comment #9
indytechcook CreditAttribution: indytechcook commentedHere is a patch with @Damien's suggested changes from comment #6. I don't expect this to be at the end state, just a next step.
I would still rather see the objects cached in _menu_load_objects but I'll code whatever you agree upon.
Comment #11
catchDowngrading this from critical. Would be good to fix but it doesn't block a release, we need to document somewhere that modules should either do static caching in their router loaders or use plain wildcards.
Comment #12
indytechcook CreditAttribution: indytechcook commented@catch see #238695: Document static cache requirements for menu loaders comment number 23 with a documentation patch.
Comment #13
mikeytown2 CreditAttribution: mikeytown2 commentedsubscribe from #402944: Persistent caching for unpack_options() calls from building displays
6.x patch attached
Comment #14
Damien Tournoud CreditAttribution: Damien Tournoud commented@mikeytown2: please read #6: there is only one case when we can safely skip loading the menu objects. Your patch will break stuff down the road if (for example) the title callback wants to use one of the loaded objects.
Comment #15
Mixologica whole lot of cranial rectal evidence
Im going to dig deeper into views on this now to see about static caching the displays.
Comment #16
apadernoI re-rolled the patch basing on what I understood from comment #6 from Damien.
Comment #17
dixon_Here is a reroll from #16 for D6.
Comment #19
apadernoComment #20
mikeytown2 CreditAttribution: mikeytown2 commentedIn regards to #6, #13, and #14 for D6 this is the logic I've come up with
It's highly specialized as the
access_callback
is set toviews_access
for views. Gives over a 250ms improvement on our test server for a front page load.Stock:
Total Self: 76ms
Total Cumulative: 675ms
_menu_link_translate() calls: 684
D6 Patch:
Total Self: 81ms
Total Cumulative: 404ms
_menu_link_translate() calls: 684
Comment #21
johnv#16: 753064_avoid_calling_menu_load_objects.patch queued for re-testing.
Comment #23
johnv#9: 753064.menu_inc-8.patch queued for re-testing.
Comment #24
johnvThe proposed patches are copy-ing existing code.
1. (!is_numeric($item['access_callback']) is already contained in _menu_check_access().
1. ($item['title_callback'] != 't' && !empty($item['title_arguments'])) is already contained in _menu_item_localize();
#1978176: Build menu_tree without loading so many objects contains another approach, by trying to load the object as late as possible, and to execute _menu_load_objects() only in menu_unserialize(). It's not perfect, yet, and needs help.
#1978176 avoids some other loading problems, too.
Comment #25
mikeytown2 CreditAttribution: mikeytown2 commentedTesting D6 patch from #20 and will move issue back to D7 once this passes.
@johnv
The D6 patch I've been using will skip loading the view if there are no access checks for it; access argument =
array(0 => TRUE)
Comment #26
mikeytown2 CreditAttribution: mikeytown2 commented#25 passes, moving this back to 7.x