Problem/Motivation
PermissionsHandler:moduleProvidesPermissions costs quite a lot and so should be static cached.
Proposed resolution
Static cache result
Let ::getPermissions accept an optional module name.
Remaining tasks
Patch
User interface changes
None
API changes
New optional argument to PermissionsHandlerInterface::getPermissions - BC is maintained
Beta phase evaluation
Issue category | Task because performance |
---|---|
Issue priority | Normal, but could be major because performance |
Prioritized changes | The main goal of this issue is performance. |
Disruption | Non-disruptive as API changes are backwards compatible |
Comment | File | Size | Author |
---|
Issue fork drupal-2339487
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 #1
BerdirBut it is also not called a lot.
Previously, we had static caching in Simpletest and it was extremely annoying, because when you added things like node types or filters, or something else that dynamically creates permissions, you had to call a weird, magic simpletest method to clear that cache.
It might be a bit better if it's part of the official API, but still there is a reason we do not do static caching at the moment.
Comment #2
dawehnerYeah, the only place where this is a problem is AdminController::index() system_get_module_admin_tasks and so also PermissionHandler::modulesHasPermission() is called once per module so you end up with a lot of calls. I think static cache for that
method is enough, no need for the permissions itself (as they also cause the test failures, if enabled).
Comment #3
larowlanComment #4
larowlanUpdated issue summary, added beta template
Here's a patch
Comment #6
larowlanone day I'll remember to run unit tests first
Comment #7
jibranBlank comment line missing between @param and @return also desc of @return is not correctly indent.
Comment #8
Fabianx CreditAttribution: Fabianx commentedtypo - permisisonsCache
its used consistently though ...
If we want to do that, getYamlDiscovery() should take a modules parameter as well - instead of copying logic directly in here.
As Berdir I am not yet convinced on this, how often is the static cache used anyway?
This needs some up-front profiling, I think.
In general it makes sense, but its in the details ...
Comment #9
dawehnerThe only place where this kinda matters is when you install Drupal via CLI, as far as I understand, because in that case you parse the permissions
multiple times per request. Once for every user_modules_installed() call.
Comment #10
BerdirRight, but does that actually run multiple times, the admin role does't exist until the profile configuration is installed, which is the last module of an installation?
I'd say lets wait and see if #2435075: Implement admin role as a flag on the role storage, simplify permissions page, remove user_modules_installed gets in, if so, we can probably close this as won't fix/duplicate?
Comment #11
dawehnerYeah let's wait.
Comment #12
Fabianx CreditAttribution: Fabianx commentedFor user_modules_installed() this would not have helped in the form it is here.
I think that was intended for the admin usage in system_admin_tasks or such ...
But yes, lets wait.
Comment #13
jhedstromNow that #2435075: Implement admin role as a flag on the role storage, simplify permissions page, remove user_modules_installed is in, is this still needed? I came across this issue from the
TODO
in the code that is being worked on over at #2342229: Add permissionByModule to user.permissions service. If this is now resolved, that TODO should be removed.Comment #14
dawehnerWell, you could have a look, I think
admin/index
is still slow, due to that.Comment #15
jhedstromPatch from #6 still applies. I profiled
admin/index
with and without the patch:Comment #16
Fabianx CreditAttribution: Fabianx commentedI think we should do this :)
Comment #17
dawehnerIts a little bit odd that we just static cache the module one, but I guess this is the primary usecase we have. We can iterate later, if needed.
Comment #18
Fabianx CreditAttribution: Fabianx commentedSorry, CNW per review from #8
Comment #19
BerdirOnce again, wondering how much of an improvement remains here when we have the file cache issue in. We need to get that done :)
Comment #20
jhedstromThis should address #8.
Comment #21
claudiu.cristeaWe will load the permission list also here #2571235: [regression] Roles should depend on objects that are building the granted permissions. So I think we need not only a static but a persistent cache :(
Comment #25
jncrucesIs this patch usable for a production site?
Every time i access to extend page i get a timeout. Normally i only comment the lines of permission in ModulesListForm.php (lines 224 - 232).
Comment #27
BerdirWe have a few cases now where this would be useful, for example the mentioned #2571235: [regression] Roles should depend on objects that are building the granted permissions and also #2934812: Use render caching on permissions UI table that I just opened. And those cases actually need the permissions, not just whether or not a module has permissions.
So lets try to statically cache the actual permissions list. I have an idea on how to solve the testing problem (basically just reset the cache whenever we validate permissions in tests).
Based on some quick profiling, the previous patch was 54% faster than HEAD on admin/index, this one is about 7% faster again but not sure if I trust that number and that would also only be true if moduleProvidesPermissions() is called for most/all installed modules, but if so, then getting the permissions and sorting them just once is a slight advantage. And not calling it multiple times for the same module because I removed the per-module static cache.
One advantage of this is also that it becomes trivial and doesn't require any test or interface additions.
Comment #29
BerdirLike this. We could also add an explicit method, but then we need an interface change again.
I think we've done enough profiling?
Comment #31
BerdirSo a single actual test fail and that's a update test that tests that new permissions are added. I think that's OK to break that?
Comment #32
phenaproximaWe should probably add a resetCache() method or something, so that it's possible to clear the static cache explicitly if needed...no?
Comment #33
BerdirI thought about that, not sure. That would require an additional method on the interface.
The only use cases to do that are tests IMHO, for that resetting the container might be sufficient?
Comment #34
phenaproximaI could imagine possible use cases outside of tests, like if your code has installed or uninstalled a module and needs to refresh the set of available permissions for some reason. I think it would be worthwhile to add the method (which is allowed in a minor release as long as there's a base implementation), because without it, any code that finds itself needing to refresh the list of permissions in a non-testing context is up you-know-which creek without a paddle. :)
Comment #35
Berdir> like if your code has installed or uninstalled a module and needs to refresh the set of available permissions
1. Installing/uninstalling module creates a new container => new permission handler object, cache is gone
2. We don't need the list of permissions to check if you have a permission, we only need it for validation in tests and some UI elements.. the permission page and the modules/admin index page tocheck if a a module has permissions or not.
I'm not against it, just think that we should have a good reason to extend an interface with a method, even if it's very unlikely that someone has an alternative implementation.
Comment #36
dawehnerWhat about we add the method when something actually has a usecase for it?
Comment #37
larowlanAgree
Comment #40
joachim CreditAttribution: joachim as a volunteer commentedThis should be a permanent cache, not static.
The use case is places in the UI that refer to a permission by its human-readable label, and which currently violate DRY by repeating the label that's defined in permissions.yml.
For example, on the user admin settings page:
(For at least a while, the permission label here didn't match the one in user.permissions.yml! That bug is fixed in 8.8.x at least!)
Comment #41
BerdirA persistent cache causes a lot of problems with dynamic permission, I'm not convinced that's worth it because nothing uses permission labels in the critical path and that UI not using the label is a different issue IMHO.
Comment #43
Oscaner CreditAttribution: Oscaner at CI&T commentedComment #45
andileco CreditAttribution: andileco at JSI Research & Training Institute, Inc. (JSI) commentedWe were getting locked out of the /admin/modules page, and applying #29 on 8.8.6 fixed this for us. We will try the other patches too. Just wanted to bump this a bit, as without this patch, we couldn't enable modules.
Comment #46
narendra.rajwar27Comment #47
narendra.rajwar27Adding patch for 8.9 and 9.1 branch.
Comment #48
narendra.rajwar27Comment #50
Nigel CunninghamI'm currently building a rather large site with quite a lot of modules enabled. With this patch enabled, /admin/modules went from timeout out after 70s to loading in 23s (both with profiling enabled). The profiling led me to this patch via the @TODO comment in moduleProvidesPermissions.
The patch itself looks good to me, but needs some tests and therefore needs work.
Regards,
Nigel
Comment #53
drupalvikingI had a problem with a site that I upgraded from 9.0.6 => 9.3.3 I had not worked on it prior to the project's completion, but before I upgraded I did access the /admin/module page at least once.
After updating, I just got 504 Gateway Timeout, but locally and on the staging server, only when accessing the Module list page. After some hefty debugging, I was able to find a comment that lead me to this page. I applied #47 with composer and now it works just fine. It's still relatively slow, and I'm not sure why this is happening on this project alone, but at least, I can confirm that the patch fixes my error, and from my behalf, it has been tested by the community.
Comment #54
joegraduateComment #58
almunningsWith patch
Drupal 10.1 gets a heap of weird issues on installing modules.
Specifically with Drupal\Component\DependencyInjection\ReverseContainer
Service Container has a null value for generateServiceIdHash.
Running a drush cim that would install a module, occasionally throws a delightfully difficult error to trace.
It's a result of
\Drupal::getContainer()->set('user.permissions', NULL);
Comment #59
mglamanThis shouldn't use a cache property. It should use a memory cache backend that falls back to a permanent cache backend.
Like JSON:API does:
This allows for invalidating the in-memory cache within one request.
Comment #60
catchJust read down and was thinking the same as #59, and then there was the comment so I didn't have to write it :)
Although given #41 I think we should add the memory backend here and then move persistent caching, if it's needed at all, to a follow-up.
Comment #63
kim.pepperPushed an MR that adds a MemoryCache.
Comment #64
kim.pepperLots of test fails for this. I assume we need to work out where the cache needs to be cleared.