Problem/Motivation
See notes in #3478621: Add filecache to OOP hook attribute parsing.
Mostly for YAML discovery, we cache the parsing of files in FileCache, which is a raw APCu cache implementation using filemtime for invalidation. The idea is to persist file parsing across cache clears (because it's not a cache backend), so that multiple module installs or cache clears and re-use parsing of identical files.
APCu isn't shared between cli and web processes, nor is it shared between cli processes or - therefore whenever YAML (or soon attribute) parsing tries to use FileCache on the cli, it won't work. Similarly, it's not shared between multiple web heads either.
Many of the things in FileCache are very expensive to generate at the level of a request (e.g. hundreds of milliseconds), but also over the lifecycle of a site have very low cache hit rates because they are often multiple caching layers on top.
If we can add a persistent, shared version of FileCache for most discovery, it should accomplish two things:
1. Increase the cache hit rate compared to storing in apcu
2. Reduce the amount of data we put into apcu by quite a lot, freeing it up for things accessed more frequently.
Steps to reproduce
Proposed resolution
File cache has the benefit of persisting in-between cache clears (which for some use-cases is almost the only time it runs like router rebuilds). Therefore add a new cache bin cache.file_parsing which is not tagged as a cache bin so therefore won't be affected by drush cr or cache clears via the UI.
On top of this cache bin, add a YamlCacheCollector - this can be used for a (not quite because the APIs are different) drop in replacement of FileCache. Each discovery should use its own instance of the class with its own cache key so we don't dump all yaml files in single cache item.
On top of YamlCacheCollector, add a discovery implementation too for use with routing/plugins.
Remaining tasks
To use this for css and js minification we'll need a non-YAML CacheCollector that does minification instead of YAML parsing - this might be able to use a base class or trait to share some of the logic.
Similarly, we should be able to provide an attribute cache collector similar to the Yaml one, for attribute plugins.
Both of these may end up in their own individual follow-ups depending on scoping decisions.
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #70 | Screenshot from 2026-05-22 11-09-22.png | 179.2 KB | catch |
| #70 | Screenshot from 2026-05-22 11-07-27.png | 420.15 KB | catch |
| #52 | Screenshot from 2026-05-19 20-44-23.png | 300.94 KB | catch |
| #52 | Screenshot from 2026-05-19 20-44-15.png | 398.54 KB | catch |
Issue fork drupal-3486503
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
alexpottEek... but yeah this has always been a downside. This brings me back to a micro/precompiled container that that has essential services. FWIW we do have something similar already with \Drupal\Core\Config\BootstrapConfigStorageFactory...
Comment #4
catchPipeline is green.
There is one remaining thing, which is we need a new FileCacheGarbageCollectionInterface, implement it in the new class, call the ::garbageCollection() method in system_cron() if the service implements the interface. That will complete the expires logic added so the database table doesn't have stale and un-removable cache entries in it forever.
Comment #5
catchPushed a commit to implement #4.
Comment #6
nicxvan commentedUnfortunately lots of failures that seem relevant, have not reviewed any.
Comment #7
catchIt's my attempt at garbage collection breaking. Pushed a commit which fixes at least one test.
Comment #8
catchMR is green again now. Updated the issue summary with a summary of the implementation.
Tagging beta target because I think this is needed to mitigate the memory usage implications of the OOP hooks bc layer and new attribute parsing in core generally.
Comment #9
nicxvan commentedDid a quick review, a few questions where I feel like I'm missing something obvious.
Comment #10
nicxvan commentedMy questions were answered I'll do a more thorough review later unless someone else gets to it.
Comment #11
nicxvan commentedJust one final comment that I think needs addressing.
Comment #12
catchAddressed the last comment. Did some manual testing of this with #3478621: Add filecache to OOP hook attribute parsing and got what I would expect running the UI installer with the standard profile. Comments over there.
When doing that testing I realised another thing.
FileCache forces use of the apcu_prefix in the cache key. I think we should move that down to the apcu backend so the database doesn't use it - this would enabling persisting file_cache entries across deployments which is potentially a few hundred milliseconds downtime and dozens/hundreds of mb of peak memory usages removed from every deployment. However will start a new issue to explore that and we can either merge it back into here or do it in two issues depending on what it looks like.
Also notable is that I'm only seeing hook implementations go into the cache, nothing from YAML parsing - need to refresh my memory on what we do there but looks like we might need to either fix or add some caching to library/info/config YAML parsing.
Comment #13
alexpottI think we want to add the table to the schema only suggestion in \Drupal\Core\Command\DbDumpCommand::configure() - we also need an issue against Drush to update the schema only defaults stuff there for db dumping.
Comment #14
catchThis is done.
Happy to open this one once we've finalised the table name.
Comment #15
catchThis was bad testing on my part - I already had these cached in APCu from a previous installation attempt. Did a ddev restart, emptied the database, reinstalled, and there were 317 rows just from hitting the first page of the UI installer with a pre-filled settings.php.
After install and hitting the front page, there were 1226 rows from various discovery (library, info etc.) - this was without the hook discovery caching MR.
Shows that there are definitely advantages to the APCu cache for situations like repeatedly reinstalling Drupal as well as test pipelines.
Comment #16
catchOpened #3488925: Move the FileCache apcu prefix handling into the apcu implementation or allow the APCu prefix to be customised and in the process of writing the issue summary, decided it definitely needs to be its own issue if we do it at all.
Comment #17
nicxvan commentedFailures look random, but I cannot rerun.
Comment #18
nicxvan commentedCouple of relevant failures, they seem the same type, expecting no tables but db cache exists
Comment #19
catchMoving discussion from the MR to here.
@alexpott brought up the test expectation change in InstallerExistingSettingsTest.
We added that in #3103529: Drupal 8.8.1+ and 9 can fail to install in the web browser due to cache pollution to prevent cache pollution when hitting index.php then getting redirected to the installer.
I had a look at what changes would be necessary to prevent the table from being created in the early installer phase (when settings.php is pre-filled), and I wasn't able to find a good way to do this - because of the singleton approach in FileCacheFactory as opposed to it being a service (which is necessary for it being a dependency for container building), finding somewhere that's early enough and also being able to detect the installer state is not really doable. One possible option I didn't try yet (because it seems like a huge hack) would be checking for the install state globals in the implementation. Our entire 'early installer detection' relies on 'has a table in system_schema() been created yet' which itself is a massive hack that needs to be replaced too.
However, the bug we added that test for is for the regular cache backends which don't do mtime invalidation, so I think it's fine for this table to be created when settings.php is filled, the content of the cache doesn't vary by the install state or not, it just varies by the contents of the file - so why not rely on it in that very early install state.
Comment #20
catchTests are back to green.
Comment #21
nicxvan commentedOne more comment on the mr once that is addressed I think this is ready. I think @alexpott should confirm his concern has been addressed.
Comment #22
catchFixed the one comment on the MR. Also realised we can/should take the database connection out of the constructor - theoretically at least this can be used without needing one, so might as well get the connection as late as possible.
Comment #23
nicxvan commentedI can't rerun the failed jobs, but only one looks like it might be relevant.
Comment #24
catchRe-ran the three failing jobs - all green now.
Comment #25
nicxvan commentedFrom my perspective this is RTBC, @alexpott mentioned on slack he wants to review it again.
Comment #26
nicxvan commentedThis is ready I think.
There are two open comments by @daffie, I think both have been addressed appropriately.
The check does need to be for default not active.
And the next time we need normalizeCid it should be factored out, but not yet.
Alex's concerns have also been answered.
Comment #27
nicxvan commentedBased on @alexpott's comment @daffie's comment was not actually resolved.
Comment #28
catchI looked a bit more and I'm not really sure about @daffie's comment either.
With ::getConnectionInfo() we ensure the default database connection is configured, and then get a connection if it is.
With ::isActiveConnection() we check if there's an active connection (which will be the default database connection if it's active whenever this would be called), but then if it's not, we won't try to start a new connection.
To me ::getConnectionInfo() seems more robust, because it's OK if we start a new database connection here, we just need to know we have one to start.
In practice this might not actually make any difference to a site.
Comment #29
alexpott@catch and I discussed #28 and decided to keep getConnectionInfo('default') but then do getConnection('default', 'default') to be super explicit about what connection to use. This is because of how $key and $target work for those two methods, this class can create tables and we have good evidence that file cache is used by run-tests.sh when rendering test results!
Comment #31
alexpottComment #32
nicxvan commentedThis has the same distribution failure #3486503: Add a file parsing cache collector to replace some uses of FileCache
Comment #33
catchThe fix that just got committed for #3486503: Add a file parsing cache collector to replace some uses of FileCache should mean we get a green run again, restoring the RTBC since no code changes here.
Comment #34
nicxvan commentedcore/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.phpComment #35
catchThis test failure (not in the most recent run) is very strange, and i can reproduce it locally:
There are multiple queries that just say 'Binary String' with the same long string. I don't know if they are errors, or informational, but they are weird and being picked up by the performance test query logger (apparently only sometimes).
Comment #36
catchRealised thinking about that - we can't log these queries in performance tests as database queries, because an item may or may not exist in APCu depending on whether it gets evicted or is ever set etc. So we would need to ignore them. We could also ignore 'Binary string' queries but I'd like to know what those are ideally.
Theoretically it might be possible to add a file cache listener to log get/store separately (like we do for caches), but that's going to require overriding the backend with a custom one and can't just decorate a service.
Discussed this issue a fair bit with @berdir in slack, and he suggested possibly only opting into the database-backed cache for attribute parsing, since YAML parsing may be faster without it (and shouldn't result in unfreeable memory usage). Still need to think more about that, but this is blocked on excluding anything from FileCache in performance tests at least.
Comment #38
catchComment #39
berdirLink to the slack thread (it's long): https://drupal.slack.com/archives/C079NQPQUEN/p1732735819875639.
TLDR: This adds around 1700 database queries on my current setup (core + a bunch of contrib modules + test module discovery. The test module discovery won't be on on real sites, but sites with 100+ contrib projects will not be far off from that). And that's not even counting Hooks discovery yet, because that's part of the container and not part of my test scenario. For Yaml discovery and parsing of those small files, the database lookups seem to take more time than the actual parsing.
Also created this issue #3490347: Replace ExtensionParser::scanDirectory() FileCache usage with InfoParser
Another semi-related thing: also could reproduce reports about massive increase of installer memory usage. from 36MB on 10.4 to 260MB on 11.x, that is with drush. with drush, because it all happens in a single process, none of the current issues are able to make any real improvements to that. web installer on 11.x HEAD with the filecache seems to remain < 40MB now. Not sure there is a solution. Drush might need to change and somehow push module installs into separate batch processes? No issue created about that yet.
Comment #40
catchSo from that slack thread, about 800 database queries were due to #3490347: Replace ExtensionParser::scanDirectory() FileCache usage with InfoParser, we should postpone this on that issue.
The weird binary string 'queries' are also a blocker here I think at least until we know what they are, and so is excluding queries from the backend from performance tests since we can't guarantee whether anything comes from apcu or the db by design, so it'll need some work once that issue is done. And maybe being more selective about which file caches we apply it to (can be done with $default_config param to FileCacheFactory).
We could explore a performance logging decorator in performance_test module in a follow-up, probably could only catch things outside container building but that is fine for most of our use-cases, so even if we exclude this from performance tests which can later bring it back.
Berdir is right that the drush installer currently wouldn't benefit from this, however I would expect e.g. a drush cr after install to benefit because the entries will be cached by then (from the installer or previous drush crs).
However also from the pure memory reduction standpoint, I was seeing preprocess hooks in system module discovered as hooks and added to the container, which is a side-effect of not having #3490355: Add procedural hook short circuit per module or file.
So maybe the order should be:
#3478621: Add filecache to OOP hook attribute parsing - already in as of yesterday.
#3490355: Add procedural hook short circuit per module or file - try to reduce the amount of parsing we do.
I also noticed when checking what gets cached vs not, and it's visible in the performance test results too, that we are doing a lot of file scanning and parsing for plugins that aren't plugins.
e.g.
Only GDToolkit is the actual plugin, all the operations are not, so that is nine 'NULL' cache entries where there should be one.
If I run:
(which is looking for a filecache data NULL entry - what we use in plugin attribute parsing)
I get 139 results.
We could have an issue which adds an exception when a non-plugin is found in a plugin directory, and gradually move these out. This would save directory traversal, memory, and space in APCu. And then if/when we return here, that's 139 less database queries too. Will open a meta and a sub-issue for the GD toolkit to start.
An issue against drush to use its own batch system in drush si also sounds like a good idea, I'm a bit surprised it's not already doing that. However thinking about that, we might want to do that in co-ordination with #3416522: Add the ability to install multiple modules and only do a single container rebuild to ModuleInstaller, that issue also results in a lot less reflection/YAML parsing, although eventually the same number of files will be parsed, just not as many times. But installing x number of modules in drush, hitting one that needs a container build -> new process to continue, this would be a good balance if it's doable.
Comment #41
catchActually another thought which would be a change here but not postponed on the info parser issue.
There are two kinds of things we're using file cache for - extension, services, hook parsing which are all done during container build and therefore need to use the singleton.
However config parsing, plugin discovery, route attributes when we have them, libary parsing - these are all from services, so could depend on a service.
So... I think we could change approach here and do the following:
- add a new file_cache.persistent service, and inject this into the various places we want to use it.
- this service can be linked to a file_cache.persistent_cache_backend service which uses an actual cache backend, we'll need a no-op tag invalidator to pass in is the only additional thing.
- this can use BackendChain (not chained fast because we don't need the invalidation support) with the APCu and database backends.
- that will in turn make it easier to swap out the database backend with redis or memcache
- cache backends support both multiple get and multiple set. Because this is a new service with a new interface, we can copy those aspects of the cache backend interface (+ mtime invalidation support) and that will make it easier to do a query-per-directory instead of query-per-file and similar.
- for YAML parsing we'd need to have a non-filecache option for YAML parsing then, don't want to cache things twice..
This would not help us with hook and service discovery then, but since route and plugin attributes also load PHP classes, and there'll be as many of those potentially, we can save the memory somewhere both on web requests and cli that way.
Comment #42
alexpott@catch not all Yaml parsing is from services... the ExtensionDiscovery object is used from a few places for example. But also the config importer is not a service - it's complex there because caching config during imports and installs doesn't feel right - this stuff is never read multiple times (well apart from during tests). Unlike config schema yaml where there cache is definitely worth it.
Comment #43
catch@alexpott yeah I think this is more interesting for plugin discovery to start with.
Config - it's not read often, but also you can end up with 1000+ config files in a sync directory and maybe 30 changing when you do a sync, so a persistent cache with a 3 month TTL and no invalidation apart from mtime might still help - but probably need to check how the db cache would actually compare vs. Symfony YAML parsing.
I guess if it's not a service, could just do the implementation that's already here for that (but maybe not default if we want to avoid it for container builds).
Comment #44
catchComment #46
catchThought about this a bit more and might have a plan.
We can't do the original idea here because service and hook discovery (and potentially other discovery) can happen when there's no database available. Also if we use the same model as FileCache we'd end up with a lot of database queries (both gets and sets).
I think instead we can do something like the following:
1. Add a file_parsing.cache database 'backend' which is not tagged as a cache backend. Just a new service using an existing backend that won't get cleared on drush cr etc.
2. Add a CacheCollector subclass where the cache item is an array of mtime and data and the key is file paths. This CacheCollector should handle the mtime validation etc. It should probably also handle some garbage collection (e.g. on set, remove items from the cache if the mtime is more than a year old and they weren't requested in the request).
3. Not entirely sure about this, but to conserve memory, we might be able to directly instantiate the CacheCollector in the discovery code paths rather than using a service, this is done for the runtime theme registry already. That way when discovery is complete, we can free up the memory. If that doesn't work, then services will work.
4. We can potentially convert one usage of FileCache to use this, and/or one thing candidate that doesn't use it yet - js minification is one I'd like to use it for. Once we've got something working it'll be possible to profile the difference.
We won't be able to use this for service or hook discovery, or at least not without a lot of changes, but we should be able to use it for libraries, js/css minification, route discovery (YAML and attributes), plugin discovery (YAML and attributes) at least.
By using a cache collector, we only have one database get and one set per discovery type, so a 100% cold cache (usually the installer, or the first time this code runs on existing sites) shouldn't have too much overhead.
Where we can use this for existing FileCache implementation, it will allow us to remove some large and low hit-rate items from APCu, as well as increasing the hit rate against the new backend.
Comment #47
catchGoing to move this back to active since the original MR was more or less a dead-end, don't think there's any actual code to re-use from it.
Comment #50
catchOK made a start on a new approach.
New 'cache bin' which is normal except for not being tagged as a cache bin, this means it won't get cleared on drush cr etc. which is the whole point here.
New YamlCacheCollector class - this handles Yaml::decode(file_get_contents()) and mtime validation. Used it in library discovery to start with. Net reduction of code in library discovery that currently handles FileCache itself.
Couple of considerations:
- this increases the peak memory of LibraryDiscoveryParser because it currently doesn't have a static cache in there. I think we will save peak memory if/when we can apply this to plugin attribute parsing and it's cleaner to have it as a service. One way to reduce the peak memory would be to manually instantiate the cache collector inline in LibraryDiscoveryParser but that could mean more cache backend access/locking if it's used multiple times per request etc. Leaving it as a service for now.
- right now there is zero garbage collection at all, but we need something. I think a good start would be to override ::updateCache(), loop over all entries (that aren't getting set), and if anything is more than 2 years old, remove it, then call the parent - this will mean stale files that are never coming back get dropped eventually. If a system started using this and then stopped again, you could also get stale info that way although technically they could delete their own cache item in a post update.
To use this for plugin attributes, js and css minification etc. we'll need separate cache collectors. I think we can make a base class with an abstract ::parseFile() method which the YAML and other ones then inherit from.
Next steps:
1. Try to use this in routing. Router rebuild in Umami should show a bigger change in numbers than standard profile library discovery.
2. Add an implementation for js minification via Peast and use this to validate the base class idea.
3. Possibly try to apply to attribute parsing but I have a feeling that will be complicated by the plugin system, so maybe a follow-up if it's complext.
4. Garbage collection per notes above.
Comment #51
catchAdding route discovery support. Because routing uses YamlDiscovery, this means adding YamlDiscovery support - which means we'll be able to use that for YAML plugins too. Because YamlDiscovery isn't a service, added a subclass that accepts a YamlCacheCollector - this works and also saves memory. Did it without dependency injection to start with to get some numbers.
Before/after screenshots. Note these aren't like for like because the before includes everything that YamlDiscovery is used for and the after only includes routes, but routes are the main time in the first one, so it's possible to compare still.
Also note this is comparing a FileCache miss (on the basis that will happen on different web heads, between cli and web etc.) and a CacheCollector hit - because the goal here is better hit rates.
I got the 'before' via:
1. install standard.
2. Browse to admin/config/development/performance ready to clear the cache.
3. ddev restart && ddev xhprof on && ddev xhgui (so that apcu is empty)
4. Submit the cache clear form
For the after you just have to clear the cache via the form a couple of times.
Standard has less routes than Umami which has a lot less routes than most production sites, so I'd expect bigger savings in production.
Big advantage of this for routing is that the cache will be shared between drush cr building routes on the cli vs. something like saving a view, which also rebuilds the router.
On standard this saves about 190ms wall time and 350mb memory - but this is only applied to one of the caches on that request, there are lots of other candidates.
Comment #52
catchComment #53
catchComment #54
catchHad been wondering in the back of my head whether we should consider using hash_file instead of mtime for the file comparison, but the garbage collection is able to use the stored mtime and wouldn't be able to use hash_file() so probably better to stick with mtime, or at least we'd still need to store mtime either way or come up with a different garbage collection approach.
Went ahead and updated performance tests.
Per the issue summary, there are more places we can apply this to:
- Yaml cache collector can be used for menu links, local tasks, local actions, ckeditor5 plugins, breakpoints and more.
- we can add js/css minification caches
- attribute plugin cache
However some or all of these are follow-up material.
Updated performance tests - the updates show a single cache get after all cache bins are emptied, no set/lock etc. - this is showing the persistence working since the bin we use doesn't get cleared by the tests. The single cache get is a big different from the previous approach on this issue which has a cache entry per-file. Once we add it to more places the cache gets will go up by one per discovery type.
I think there's enough here for an initial review.
Comment #55
catchComment #56
catchComment #57
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #58
catchComment #59
godotislateThis is nice work and surprisingly straightforward. A few comments on the MR, mostly minor or nits, but a couple questions about test coverage.
Also, should the issue title be updated to mention that this is for YAML discovery only?
Comment #60
catchI was also surprised especially given how complicated the previous attempt ended up before abandonment.
Not sure about this yet.
I'm pretty sure we can apply it to css/js minification and attribute plugin discovery. Doing that here might make the scope a bit to big, but it would also allow us to add a base class or trait which encapsulates all the non-parsing logic for the cache collector (mostly the mtime checks) in a way that hopefully would result in less churn later. So.. I am thinking of trying to add support for one or the other or both, then if it feels a bit unwieldy, splitting it out after that.
If that turns into a bigger job than expected though, then yes we should only cover YAML here and just look at everything else in follow-ups.
I applied several of the suggestions, responded to a couple, and also added test coverage for the ::updateCache() override.
Comment #61
godotislateWill review MR updates later tonight, but one thought came to mind: Attribute plugin discovery is in the Component namespace, and CacheCollector is in Core. Do we need to introduce some interface in Component that a CacheCollector can implement? Or will the cache collector live only in the Core AttributeClassDiscovery subclass?
Comment #62
catchWhat I'm hoping is that we can do something very similar to the YamlCacheCollectorDiscovery added here - subclass with a couple of method overrides to change how the caching works and not too much else.
Comment #63
godotislateI don't see this test coverage? Other than that, MR looks good. I resolved a couple comments, and left couple of minor suggestions still open, though I'm fine if you want to resolve them without accepting.
I think this is good to expand to the other file caches.
Comment #64
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #65
catchOK I started to look at attribute plugin discovery, the file parsing logic is a lot more complicated than YAML, so it's going to mean refactoring parts of the component attribute parsing out to a trait, then using those methods in a new core attribute discovery that shares the trait but instead inherits from the cache collector. It seems doable but too much to review in a single MR.
I've added the base class, so I think what I'll try is opening a separate draft MR for the plugin attribute parsing, that will validate whether the base class is flexible enough. We can then move those commits over to a new issue when this one is more or less ready.
On js minification when I opened this issue we hadn't done #3505776: Retain asset aggregates for 45 days by default to reduce generation of identical files, but that issue will mean a much lower cache rate here, so while it will still help for files duplicated between aggregates, or building an aggregate with 10 files and where only one changed, that's still a very low cache rate, and other asset changes in the queue should progressively make it even lower (by making the hit rate on aggregates much higher which is what we want). So going to hold off that bit for now.
Comment #66
godotislateI wonder if doing so could help out with #3586328: [11.x] Handle attribute discovery fatal errors on missing traits for routing and other subsystems, at least with organizing things into reusable methods that would be useful across attribute discovery for different things.
Comment #68
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #69
catchStarted the attributes branch.
Attribute parsing doesn't neatly fit into the cache collector API because you need more than the filename to parse the attribute, so the ::get()/resolveCacheMiss() pattern breaks down a bit. On the other hand it's easy to inline a bit of that logic into ::findDefinitions() and those other methods won't get called - probably need to BadMethodCallException from them.
I've done enough to the branch that it doesn't error, however it doesn't do dependency injection or bc properly yet and there are multiple layers of that to untangle (both the discovery and the various plugin managers will need various constructor bc layers). Haven't profiled it yet either. If it works enough to profile though, that should give us some numbers to look at - at which point can move the branch to its own issue and there should be some motivation to work on the constructor deprecations at that point.
Comment #70
catchVery initial profiling of attribute discovery changes - just the standard profile saving the clear caches form. The form itself doesn't cause a lot of plugin discovery compared to a busy front end page with entities etc. so not the biggest saving, but still visible.
One is a cache hit, the other is a cache miss (e.g. after
TRUNCATE cache_file_parsing).The big thing is we're saving about 450kb of memory on a cache hit - again we already get that when there's a filecache APCu hit, the idea is to have that more often.
Comment #72
catchI've opened #3591677: [PP-1] Add a persistent cache for plugin attribute parsing and moved the attributes MR progress over there, closed the one here.
Edit: and also #3591680: Use the YAML parsing cache collector for config file storage for another case where we parse a lot of YAML.
Comment #73
godotislateCommented on the MR that maybe it makes sense to create cache collector services like
library.parsing_cacheto pass into the other discovery classes, so that we're only passing in 1 service instead of three (cache backend, lock, time).Could maybe use a factory pattern of either a YamlCacheCollectorFactory/AttributeCacheCollectorFactory, or a FileParsingCacheCollectorFactory. Something like:
Comment #74
catchPushed the change to pass in a yaml cache collector instead of three services.
Factory pattern - didn't do this yet because I'm not sure what's best - it might be worth making any factory format-specific rather than one factory for the various formats. So start with a yaml one and see if we need an attributes one etc.
Comment #75
godotislateYeah, when I thought about all the plugin discovery subclasses and plugin managers, passing in 3 new arguments seemed like a lot.
And no opinion on whether it should be format specific, other than whether there are enough of each (or total) that it's worthwhile to use a factory or factories.
Comment #76
godotislateMerge conflict on the MR and PHPCS/PHPStan issues.
Comment #77
catchShould be happier.
Comment #78
godotislateThe deprecation message has version 11.4.0, which maybe will need to be bumped? But otherwise LGTM.
Comment #79
catchRe-titling to be a bit closer to what the new implementation is.
Comment #80
catchRebased.
Comment #81
nicxvan commentedWould we add this here? https://www.drupal.org/project/drupal/issues/3159779
Comment #82
godotislateI don't think so, but we can maybe use the db-backed file cache here in that issue.
Comment #83
catchThat one gets tricky due to the installer and container rebuilds so would definitely need to be it's own issue.
Comment #84
catchRebased this one and added an comment to the service definition.
Comment #85
godotislateRTBC +1 for changes.
Comment #86
alexpottWe need an 11.4.x / 11.x version of this... I also realised after committing that a CR is in order here.
Committed and pushed dfa3792d478 to main. Thanks!
Comment #88
catchAdded a change record.
Also pushed an 11.x backport.
Comment #91
catchPushed a small follow-up commit to main to fix the namespace of a unit test - which failed hard in the 11.x backport MR.
Comment #92
catchBackport is green - only test changes and apart from silly mistakes the conflicts were easy to fix, so moving back to RTBC.
Comment #93
alexpottCommitted and pushed 0be9248e5c0 to 11.x and fd5a6fdad51 to 11.4.x. Thanks!