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

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mixologic’s picture

FileSize
901 bytes

So I guess if you attach a patch, and then preview, your patch doesnt stick? here it is again.

dawehner’s picture

Status: Active » Needs review

Correct status

Damien Tournoud’s picture

Status: Needs review » Closed (duplicate)

Please see #638070: Router loaders causing a lot of database hits for access checks, for a lengthy discussion on the same subject.

Mixologic’s picture

Status: Closed (duplicate) » Needs review

I 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.

Damien Tournoud’s picture

Status: Needs review » Needs work

The test results speak by themselves. At the very minimum:

  • your condition is wrong. I guess you meant is_numeric($item['access_callback']) instead of !isset($item['access_callback'])... which doesn't really make any sense to me
  • if you remove the loading of objects from here, you have to put it below in _menu_item_localize() because the objects can be used when there is a title callback
Damien Tournoud’s picture

Title: _menu_link_translate() makes expensive unecessary calls to _menu_load_objects() » _menu_link_translate() might avoid calling _menu_load_objects()

Retitling. We can avoid calling _menu_load_objects() at all when translating a menu link in one specific case:

  • The access callback is a boolean,
  • The title_callback is t() and no title arguments are used

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).

catch’s picture

@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.

indytechcook’s picture

Is this a worry if _menu_load_objects are cached? #238695: Document static cache requirements for menu loaders

indytechcook’s picture

Status: Needs work » Needs review
FileSize
1.09 KB

Here 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.

Status: Needs review » Needs work

The last submitted patch, 753064.menu_inc-8.patch, failed testing.

catch’s picture

Priority: Critical » Normal

Downgrading 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.

indytechcook’s picture

@catch see #238695: Document static cache requirements for menu loaders comment number 23 with a documentation patch.

mikeytown2’s picture

Damien Tournoud’s picture

@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.

Mixologic’s picture


a whole lot of cranial rectal evidence

Im going to dig deeper into views on this now to see about static caching the displays.

apaderno’s picture

Status: Needs work » Needs review
FileSize
1.04 KB

I re-rolled the patch basing on what I understood from comment #6 from Damien.

dixon_’s picture

Here is a reroll from #16 for D6.

Status: Needs review » Needs work

The last submitted patch, 753064_avoid_calling_menu_load_objects-D6-1.patch, failed testing.

apaderno’s picture

Status: Needs work » Needs review
mikeytown2’s picture

In regards to #6, #13, and #14 for D6 this is the logic I've come up with

    // menu_tree_check_access() may set this ahead of time for links to nodes.
    if (!isset($item['access'])) {
      if (isset($item['access_callback']) && $item['access_callback'] == 'views_access' && $item['access_arguments'] == 'a:1:{i:0;b:1;}' && $item['title_callback'] == 't' && empty($item['title_arguments'])) {
        $item['access'] = TRUE;
      }
      elseif (!_menu_load_objects($item, $map)) {
        // An error occurred loading an object.
        $item['access'] = FALSE;
        return FALSE;
      }
      _menu_check_access($item, $map);
    }

It's highly specialized as the access_callback is set to views_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

johnv’s picture

Status: Needs review » Needs work

The last submitted patch, drupal-_menu_link_translate-753064-20-D6.patch, failed testing.

johnv’s picture

Status: Needs work » Needs review

#9: 753064.menu_inc-8.patch queued for re-testing.

johnv’s picture

The 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.

mikeytown2’s picture

Version: 7.x-dev » 6.x-dev
FileSize
849 bytes

Testing 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)

mikeytown2’s picture

Version: 6.x-dev » 7.x-dev

#25 passes, moving this back to 7.x

Status: Needs review » Needs work

The last submitted patch, 25: drupal-_menu_link_translate-753064-25-D6.patch, failed testing.