Problem/Motivation

Found running xhprof against drush cr, by mistake when trying to track down something else.

When the menu tree is rebuilt, MenuTreeStorage::rebuild() does this:

    $links = [];
    $children = [];
    $top_links = [];
    // Fetch the list of existing menus, in case some are not longer populated
    // after the rebuild.
    $before_menus = $this->getMenuNames();
    if ($definitions) {
      foreach ($definitions as $id => $link) {
        // Flag this link as discovered, i.e. saved via rebuild().
        $link['discovered'] = 1;
        // Note: The parent we set here might be just stored in the {menu_tree}
        // table, so it will not end up in $top_links. Therefore the later loop
        // on the orphan links, will handle those cases.
        if (!empty($link['parent'])) {
          $children[$link['parent']][$id] = $id;
        }
        else {
          // A top level link - we need them to root our tree.
          $top_links[$id] = $id;
          $link['parent'] = '';
        }
        $links[$id] = $link;
      }
    }
    foreach ($top_links as $id) {
      $this->saveRecursive($id, $children, $links);
    }

This eventually reaches this code:

  protected function doSave(array $link) {
    $affected_menus = [];

    // Get the existing definition if it exists. This does not use
    // self::loadFull() to avoid the unserialization of fields with 'serialize'
    // equal to TRUE as defined in self::schemaDefinition(). The makes $original
    // easier to compare with the return value of self::preSave().
    $query = $this->connection->select($this->table, NULL, $this->options);
    $query->fields($this->table);
    $query->condition('id', $link['id']);
    $original = $this->safeExecuteSelect($query)->fetchAssoc();
    
    if ($original) {
      $link['mlid'] = $original['mlid'];
      $link['has_children'] = $original['has_children'];
      $affected_menus[$original['menu_name']] = $original['menu_name'];
      $fields = $this->preSave($link, $original);
      // If $link matches the $original data then exit early as there are no
      // changes to make. Use array_diff_assoc() to check if they match because:
      // - Some of the data types of the values are not the same. The values
      //   in $original are all strings because they have come from database but
      //   $fields contains typed values.
      // - MenuTreeStorage::preSave() removes the 'mlid' from $fields.
      // - The order of the keys in $original and $fields is different.
      if (array_diff_assoc($fields, $original) == [] && array_diff_assoc($original, $fields) ==   ['mlid' => $link['mlid']]) {
        return $affected_menus;
      }
    }

With the standard profile (but with navigation instead of toolbar) this results in 72 select queries. This is out of 228 queries total. MenuTreeStorage is responsible for more than 122 of the other select queries too, so more than 215 out of 228.

Steps to reproduce

Proposed resolution

I think we could load all the links, either with an IN() query by the IDs or just all the links in the database, then pass that big list around and skip the individual select queries.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3493290

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catch created an issue. See original summary.

lcatlett made their first commit to this issue’s fork.

podarok made their first commit to this issue’s fork.

podarok’s picture

Status: Active » Needs review

needs review

MenuTreeStorage Optimization Added (Correction)

Comparing against 11.3 + Profile Fix baseline:


Metric 11.3 + Profile Fix + MenuTreeStorage Fix Improvement
Total Time 180 sec 152 sec 15% faster
Peak Memory 326 MB 326 MB same

XHProf Highlights (MenuTreeStorage changes)


Function Before After Change
safeExecuteSelect calls 230,127 113,787 -50%
MenuTreeStorage::doSave 77,763 57,779 -26%

Cumulative Results (vs Drupal 11.1)


Metric Drupal 11.1 11.3 + All Fixes Total Improvement
Time 537 sec 152 sec 72% faster
Memory 4.38 GB 326 MB 93% less

Log files:

  • Baseline: drush_si_profiling_standard_11_3_fix1.log (180 sec)
  • With fix: drush_si_profiling_standard_11_3_menutree_fix.log (152 sec)

podarok’s picture

catch’s picture

Status: Needs review » Needs work

The results here look really good and so does the code. It's a shame we need to use a static property, but it would be hard to do without it.

phpcs isn't happy in the pipeline though, so moving to needs work for that.

podarok’s picture

I seems like stuck with spellchecker https://git.drupalcode.org/issue/drupal-3493290/-/jobs/7875657 could you help to understand where is the issue?

joseph.olstad’s picture

Just triggered a retry for all the failed jobs. There was no cspell error. Made no sense; when that happens I will trigger retry.

podarok’s picture

Status: Needs work » Needs review

javascript webtests unrelated

podarok’s picture

after re-test it's green now

nicxvan changed the visibility of the branch 3493290-try-to-reduce to hidden.

svicer’s picture

Status: Needs review » Reviewed & tested by the community

Tested MR !14230 via YUSAOpenY distribution (https://github.com/YCloudYUSA/yusaopeny/pull/331)

Environment: Drupal 11.3.2, PHP 8.3

Tested: YUSAOpenY profile installation with small_y and standard presets.

Patch applies cleanly and works correctly. No issues with menu tree operations during profile installation and runtime.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 6245bc3f3b1 to main and 1f71e2efe92 to 11.x. Thanks!

This change looks great. Nice find and fix.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • alexpott committed 1f71e2ef on 11.x
    perf: #3493290 Try to reduce the number of database queries in...

  • alexpott committed 6245bc3f on main
    perf: #3493290 Try to reduce the number of database queries in...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.