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
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:
- 3493290-menufix
changes, plain diff MR !14230
- 3493290-try-to-reduce
compare
Comments
Comment #5
podarokneeds review
MenuTreeStorage Optimization Added (Correction)
Comparing against 11.3 + Profile Fix baseline:
XHProf Highlights (MenuTreeStorage changes)
safeExecuteSelectcallsMenuTreeStorage::doSaveCumulative Results (vs Drupal 11.1)
Log files:
drush_si_profiling_standard_11_3_fix1.log(180 sec)drush_si_profiling_standard_11_3_menutree_fix.log(152 sec)Comment #6
podarokResults on standard profile https://github.com/YCloudYUSA/yusaopeny-project/blob/3fa0c6f2a5103dad90d...
Comment #7
catchThe 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.
Comment #8
podarokI seems like stuck with spellchecker https://git.drupalcode.org/issue/drupal-3493290/-/jobs/7875657 could you help to understand where is the issue?
Comment #9
joseph.olstadJust triggered a retry for all the failed jobs. There was no cspell error. Made no sense; when that happens I will trigger retry.
Comment #10
podarokjavascript webtests unrelated
Comment #11
podarokafter re-test it's green now
Comment #13
svicer commentedTested 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.
Comment #15
alexpottCommitted and pushed 6245bc3f3b1 to main and 1f71e2efe92 to 11.x. Thanks!
This change looks great. Nice find and fix.