Problem/Motivation
I have a project with a lot of configurations (3 037, mostly because of multiple languages). The site installation from existing configuration (drush site:install --existing-config) takes about 10 minutes. I did profile the installation and this is the TOP 10 slow calls:

As you can see, Drupal\Core\Config\FileStorage->getAllCollectionNamesHelper() is the slowest operation during installation.
Also, take a look at php::SplFileInfo->isDir(). It called 13'937'541 times, and almost all the calls (13'919'940) comes from Drupal\Core\Config\FileStorage->getAllCollectionNamesHelper(), but php::SplFileInfo->isDir() is slow by itself:

This is clearly because Drupal\Core\Config\FileStorage->getAllCollectionNamesHelper() doesn't cache its result in any way. It's always calculated in runtime. It seems to me like a bottleneck.
Steps to reproduce
Install the website from existing configs and profile it. The more configs you have, the more problems you will see.
Proposed resolution
Provide at least a static caching for the results of this method.
Remaining tasks
- Provide a patch with a static caching and see how tests are going.
- Decide should it be cached and how.
- Implement it.
| Comment | File | Size | Author |
|---|---|---|---|
| #20 | ddev-site-install.png | 415.63 KB | webflo |
| #11 | callstack-3389567.png | 429.46 KB | webflo |
| #2 | screenshot_top10_after.png | 124.58 KB | niklan |
Issue fork drupal-3389567
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
Comment #2
niklanThis is a very dirty patch to see how tests will react on it.
This is TOP 10 calls during installation after fix applied.
You can see that both,
Drupal\Core\Config\FileStorage->getAllCollectionNamesHelper()andphp::SplFileInfo->isDir()disappears from the list.Drupal\Core\Config\FileStorage->getAllCollectionNamesHelper()calls went from 36'392 → 4'551, total self cost (in ms) 37'790 → 96php::SplFileInfo->isDir()calls went from 13'937'541 → 23'710, total self cost (in ms) 36'307→ 72I measured installation time by
time drush site:install --existing-configand results are:real 8m 38.06sreal 7m 9.09sIt's roughly ~15% increase in site installation speed.
Comment #4
niklanIt looks like some code will break. Can we decorate that service specifically for installation process? Because I don't think this caching is actually needed for regular runtime process, but it's a very significant improvement for installation. I don't see any cases where configurations are changed on disc during installation, hence, this caching should not do any harm.
Comment #5
andypostIs it happening during installation only?
Comment #6
niklanThis profiling results for
drush site:install --existing-configwith a specific website configuration. It is not an issue on default profiles (because they are simple and small) or a smaller website (which have fewer configs). But a multilingual website, with something like 10 languages, a lot of content types, fields, etc., configs grows in size very fast, and this is where the issue starts. The profiler is clearly shows, that the more configurations you have, the more calls forDrupal\Core\Config\FileStorage->getAllCollectionNamesHelper()which is bottlenecked by aphp::SplFileInfo->isDir()- the unique calls of which is hundreds times fewer than the actual one. In my example, it means for one unique file, it calls this check 587 times.This issue reproduced everywhere, on different developers PCs and CI's.
Comment #7
alexpottI feel we need to fund out why we're call getAllCollectionNames() and work from there.
Comment #8
andypoststatic cache is workaround, as file cache should be applied in other place
Comment #9
andypostit means filecache miss
Comment #10
webflo commented\Drupal\Core\Config\FileStorage::getAllCollectionNamesHelper gets called by \Drupal\Core\Config\FileStorage::getAllCollectionNames
Comment #11
webflo commentedI looked into CachedStorage, but CachedStorage does not cache getAllCollectionNames. It calls the inner storage immediately.
It might make sense to optimize \Drupal\Core\Config\FileStorage::getAllCollectionNames, it would benefit a few other use cases too.
The calls during installation are coming from ConfigImporter and StorageComparer.
Comment #12
webflo commentedThe performance issue is in FileStorage->getAllCollectionNames. But I think it's better to fix the problem at a higher level, because StorageComparer recalculates the getAllCollectionNames very often. This is unnecessary and can be determined at the beginning of the import, not after each sync step.
Comment #14
webflo commentedComment #15
smustgrave commentedDrupal\Tests\language\Kernel\OverriddenConfigImportTestseems to be consistently failing and believe related to the change here.Can we get after profiling for this change?
Comment #17
webflo commentedOverriddenConfigImportTest passes.
Comment #18
webflo commentedComment #19
borisson_I think this looks great, but like @smustgrave asked in #15, seeing numbers on the reduced amount of calls would be great.
Comment #20
webflo commentedI profiled it with xhprof, ddev, and standard profile (drush si --existing-config -y). Filtered the list down to getAllCollectionNames. There are 330 fewer function class than before.
Comment #21
smustgrave commentedBelieve based on #20 this is a net improvement for sure.
Comment #22
catchI'm not entirely sure about this one.
In
StorageComparerthere are two calls to ::getAllCollectionNames().1.
createChangelist- this builds the change list, which is cached.2.
hasChangesthis gets all of the collection names, then checks them against the changelist.What I'm wondering is - could ::hasChanges() directly check the changelist -e.g. do any of the collections in it have any of the operations, and if so, would that reduce the number of calls? If that worked, it would mean no extra static caching. If it doesn't work, it would be good to know where the other calls to ::getAllCollectionNames() are coming from.
Comment #23
catchComment #24
smustgrave commentedPer #22