Problem/Motivation

I'm investigating a site install profile that takes over 4 minutes to install. As a part of that, I noticed that nearly 20% of the installation time was in calls to \Drupal\Core\Update\UpdateRegistry::getRemovedPostUpdates() and its child functions.

This MR adds in a static memory cache so that each module is only ever scanned once per bootstrap.

Here's a diff of a custom profile from before this change and after.

xhgui showing ~20% improvement

And for something more reproducible, here's demo_umami with a 2% improvement:

xhgui showing 2% improvement.

Steps to reproduce

I think this is best tested on sites with many, many modules and configuration exports. In this case, the site has ~350 modules and ~2800 config objects. Looking at the test runs, it doesn't appear this improves the standard or testing profiles at all, but perhaps it shows up with umami.

Proposed resolution

Add a static cache.

Remaining tasks

Confirm this isn't a bc break for backporting to Drupal 10.

User interface changes

None

API changes

None, constructor changes only.

Data model changes

None

Release notes snippet

Issue fork drupal-3414349

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

deviantintegral created an issue. See original summary.

deviantintegral’s picture

Issue summary: View changes
StatusFileSize
new535.04 KB

deviantintegral’s picture

Version: 10.2.x-dev » 11.x-dev
deviantintegral’s picture

Issue summary: View changes
deviantintegral’s picture

Issue summary: View changes
StatusFileSize
new442.6 KB
deviantintegral’s picture

I'm kind of amazed tests are passing. With that in mind, I've rebased against 11.x, though this patch applies cleanly to several prior Drupal versions as well.

I haven't added new tests, because this feels like exactly the sort of thing that should be transparent to calling code.

deviantintegral’s picture

Status: Active » Needs review
deviantintegral’s picture

Issue summary: View changes
smustgrave’s picture

Status: Needs review » Needs work

Left some comments

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

taraskorpach’s picture

Status: Needs work » Needs review

I've updated the MR based on the comments. I've also added change records. Since this is my first time doing this, it would be better to have it reviewed.

taraskorpach’s picture

Assigned: deviantintegral » Unassigned
deviantintegral’s picture

Status: Needs review » Reviewed & tested by the community

I made some minor changes to the CR formatting, as I believe we use human-readable sentences. Otherwise, the code changes look good!

taraskorpach’s picture

Hope it's okay within the Drupal community to interrupt in the middle of resolving an issue and make some MR changes. I just wanted to save you some time :)

deviantintegral’s picture

Totally, I forgot to unassign it for the weekend anyways.

catch’s picture

Status: Reviewed & tested by the community » Needs work

I think we we can improve things further by moving the caching down a level - see comment on the MR.

deviantintegral’s picture

Status: Needs work » Needs review
catch’s picture

It'd be worth comparing the detail for ::scanExtensionsAndLoadUpdateFiles() with the two approaches - if we're also checking update hooks during install (which we should be), then we should see savings from that which we won't see when comparing getRemovedPostUpdates()

deviantintegral’s picture

StatusFileSize
new904.12 KB
new351.84 KB
new737.55 KB

Here's three different profiles testing against Umami. These all compare against a base of b1fbff58cca271a88373115ceb3a818bff1762d2 which is on the 11.x branch.

First: comparing against 5605d11412 from this merge request. This is the first pass with a cache just around getRemovedPostUpdates():

Overall pretty decent, a 2% reduction in time and ~182K less function calls.

Second: comparing against c30cc7f014 which moves the cache to scanExtensionsAndLoadUpdateFiles(). While there's still around 182K less function calls, it actually took ~455ms more CPU time.

I was curious and wondered if the additional calls to delete() needed when caching that whole function were related. So here's a third comparison against 5605d11412 with the additional call to clear caches as needed to pass tests when caching scanExtensionsAndLoadUpdateFiles().

As we can see, the additional delete() calls clearly have an effect.

With the above in mind, I think we should go with the first approach where we just cache getRemovedPostUpdates. While the second approach may be conceptually better, in all cases so far it seems to be slower than the first approach, both in a small install profile like Umami and in a much larger install profile.

catch’s picture

I think I might have found an issue in the new caching logic - see comment on the MR.

smustgrave’s picture

Status: Needs review » Needs work

Appears to be 1 open thread, have not reviewed or tested.

taraskorpach’s picture

Status: Needs work » Needs review
alexpott’s picture

Priority: Normal » Major

This is at least major. Fixing this will speed up site installs loads.

catch’s picture

There's only one failure in https://git.drupalcode.org/issue/drupal-3414349/-/jobs/664042 - so things might be close with the current approach?

alexpott’s picture

StatusFileSize
new132.5 KB
new130.22 KB

I've just opened up a new MR that refactors UpdateRegistry a bit. I no longer scans for all update functions for all extensions when doing getUpdateFunctions()... it only looks are update functions for the provided $extension_name.

It also static caches if calls loadUpdateFile() for a particular extension / root /site / update type combination and then does not redo work in scanExtensionsAndLoadUpdateFiles() because once a file is included in PHP it cannot change and it cannot be unloaded.

I've got a legacy site where I've applied this MR and #3406929: Configuration being imported by the ConfigImporter sometimes has stale original data (which also has a big impact on what happens in \Drupal\Core\Update\UpdateRegistry::onConfigSave() and this results in the follow xphrof changes...

xhprof changes

This MR is responsible for:

xhprof changes

smustgrave’s picture

Status: Needs review » Needs work

There are 2 MRs, 1 all green and 1 with a test failure, which one is to be reviewed?

deviantintegral’s picture

Nice! I like that we can go back to a simple static variable, I hadn't thought about the fact that PHP won't reload files once loaded. This also saves Drush from having to coordinate a release around this change (not that it's core's responsibility, but it's nice to save downstream users work even for constructor changes).

https://github.com/drush-ops/drush/blob/1f9e78ad1aace1efa26bbaaa2e30c628...

deviantintegral’s picture

Status: Needs work » Needs review
smustgrave’s picture

Do wonder if a performance test should be added for this.

catch’s picture

To add performance testing for this we'd need to be able to count function calls via zend_observer which requires a PHP extension. This is not out of the question and something I'd love us to be able to do eventually, but it'd be a completely new thing compared to the current performance testing and quite a lot of work to set up.

alexpott’s picture

I deleted the CR because it was no longer relevant to the new approach. This would be great to land.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Seems all feedback has been addressed then

@catch if you think it's worth it I can open a follow up for the testing.

Would say this may fall under a task maybe but will leave as is.

  • catch committed 1c2c5636 on 10.2.x
    Issue #3414349 by deviantintegral, taraskorpach, alexpott, smustgrave,...

  • catch committed 10322daf on 11.x
    Issue #3414349 by deviantintegral, taraskorpach, alexpott, smustgrave,...
catch’s picture

Version: 11.x-dev » 10.2.x-dev
Status: Reviewed & tested by the community » Fixed

@smustgrave I don't think we've got an issue open for counting function calls, but it would be good to have one yeah, would probably need to be a plan since it'd be a multi-step thing.

MR looks good and don't have any comments, so committed/pushed to 11.x and cherry-picked to 10.2.x, thanks!

Status: Fixed » Closed (fixed)

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