Problem/Motivation
On a site with ~20 content types, and quite a few modules, there may be a few hundred permissions at /admin/people/permissions. In this case, the web-page loads relatively quickly (network) but then flatlines the CPU at 100% for a long time (70 seconds on my 2.4GHz Macbook Pro, Google Chrome). Roughly same results in Safari and Firefox.
I will use Speed Tracer http://code.google.com/webtoolkit/speedtracer/ to investigate this a bit further, but initial results in attached screenshot.
Additionally, it's possible for the permissions page to exceed max_input_vars and silently drop part of the form submission.
Proposed resolution
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#48 | Speed Tracer D7.9.jpg | 112.96 KB | lyricnz |
#47 | drupal.user-permissions-js.47.patch | 3.89 KB | lyricnz |
#27 | Speed Tracer.jpg | 181.8 KB | lyricnz |
#28 | Speed Tracer-1.jpg | 168.88 KB | lyricnz |
#25 | drupal.user-permissions-js.25.patch | 4.71 KB | sun |
Issue fork drupal-1203766
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
catchSubscribe, quality screenshot.
Comment #2
lyricnz CreditAttribution: lyricnz commentedThe problem is basically that user.permission.js adds/toggles checkboxes all across that page, each of which causes a ~50ms layout operation. See attached stack trace (hundreds are the same), and source code.
Comment #3
miro_dietikersubscribing.
Indeed the permission table is something that grows continuously till it's blocks everything...
The screenshots are a very nice starting point.
Comment #4
xjmThe behavior was introduced in #78941: Usability: Auto-check permissions if "authenticated" has them.
Comment #5
pillarsdotnet CreditAttribution: pillarsdotnet commentedProposed fix:
Comment #6
xjmMoving to 8.x and tagging for backport.
Comment #7
sunTest again with attached patch, please?
Further performance optimizations are possible, these are the most obvious and simplest fixes.
Comment #8
lyricnz CreditAttribution: lyricnz commentedSorry @sun - no big change. Here's a stack trace of the problematic method, from an uncompressed jquery.
FWIW, if I hook_form_alter() the JS off the user_admin_permissions form, the page loads in a couple of seconds, rather than about 70.
Comment #9
sunHence, attached patch should fix it.
Sometimes, there's no other way than to circumvent jQuery. I ran into similar performance issues in some contrib projects too already.
Comment #10
sunSorry, .triggerHandler() is incompatible here. Fixed that and explained in a comment.
Comment #11
lyricnz CreditAttribution: lyricnz commented@sun #10 is about ten times faster than the current code, and looks okay visually. Still takes ~6s here with ~280 permissions and 7 roles (don't ask).
Comment #12
sun6 seconds still sounds like too much. Though the pure rendering of 1,960 table cells and checkboxes probably takes a second on its own already. Do you have new details on where the time is spent?
Comment #13
lyricnz CreditAttribution: lyricnz commentedHere is updated Speed Tracer. It's spending the same amount of time as before doing the JS callbacks (5.9s) but now only 0.2s doing layout. Hard to tell exactly what it's doing: the line that's actually being reported as consuming the time is jquery-1.4.4-uncompressed.js::[unknown]() Line 873:
I'll have a bit more of a look now.
Comment #14
lyricnz CreditAttribution: lyricnz commentedThe problem is ultimately that we're adding 1300 checkboxes to an active DOM. Hmm, a quick hack here, and it looks detaching the table from the DOM, performing all the modifications, then re-adding it - takes 2.7 seconds instead of 5.9s:
I'm not a JS guy, so I may be doing something totally crazy here...
Comment #15
lyricnz CreditAttribution: lyricnz commentedBTW, I am currently benchmarking a few aspects of the code used in this code:
Comment #16
lyricnz CreditAttribution: lyricnz commented(Using patch in #10 as a baseline. Results are for Chrome+Firefix 5)
1) :checkbox vs input:checkbox vs [type=checkbox] vs input[type=checkbox]
input[type=checkbox] is about 20 times faster than :checkbox that we're using.
see http://jsperf.com/big-checkbox-test/2
2) ':not(x)' vs .not(x)
.not() is about twice as fast as :not
http://jsperf.com/big-checkbox-test/3
3) meh, maybe later
4) detached table vs attached
For a large DOM, detached is about twice as fast - see #14
Comment #17
lyricnz CreditAttribution: lyricnz commented$('input[type=checkbox]').hasClass('.rid-2');
vs
$('input[type=checkbox].rid-2');
the latter is 15% faster (duh :)
Comment #18
lyricnz CreditAttribution: lyricnz commentedHere's a patch that:
- based on patch from @sun in #10
- detach the table from the dom before manipulation
- replace :checkbox selectors with input[type=checkbox]
- replace :not selectors with .not()
The JS completed in 2.4 seconds on the same machine as quoted previously.
Comment #19
fubhy CreditAttribution: fubhy commentedI am just thinking here... But... Wouldn't it make sense to introduce some kind of maximum number for which we keep on using the full-list and when we breach that number (like 500 rows or so) we instead display a Select Dropdown which lists all the permission categories and then on select we print the list of that permission categories with AJAX? There could still be a button to "show all". Or it could be a multi-select so the user can choose more categories to display at once.
Comment #20
lyricnz CreditAttribution: lyricnz commentedWhether that's the right solution or not, we certainly do need a better UI design for this page. Suggest you open a new issue? This one is kindof an emergency fix for catastrophic performance, rather than fixing the UI.
Comment #21
sunThanks @lyricnz! Very inspiring :)
I've fixed some trailing white-space, added comments, and also fixed the bogus re-insertion of the table by dynamically using .prev() with .after() or .parent() with .append().
I think this patch is ready to fly now.
Comment #22
lyricnz CreditAttribution: lyricnz commentedWorks well for me.
Comment #23
catchTagging. I am no good at js but have been following via irc and the issue, and this is really nice work. The summary is that with lyricnz's test case, time spent is down from 65 seconds to 3.
Comment #24
webchickNICE job on this!! Holy cow.
Committed and pushed to 8.x and 7.x!
Comment #25
sunHere's the alternative, dynamic on-demand approach we briefly discussed in IRC.
It will probably work fast on @lyricnz' sample. Less so on sites that actually have the auth users checkbox ticket for almost all roles. So kinda use-case dependent - although I think that @lyricnz' sample is more common, especially due to the new admin role feature in D7, but also, because I'd imagine sites granting lots of permissions to auth users to be smaller in general (i.e., having less roles and permissions in the first place).
Nevertheless, since the committed code is quite a major workaround for performance, it would be a good idea to investigate, benchmark and compare this alternative approach.
Comment #26
sun@lyricnz: Any chance to test #25? I think the on-demand approach superior.
Comment #27
lyricnz CreditAttribution: lyricnz commented@sun: Slower (3.4s vs 2.4s with previous patch). Caused by 58 layouts, caused by the DOM being attached during the toggle.
Edit: attached screenshot shows a Layout caused by something else.
Comment #28
lyricnz CreditAttribution: lyricnz commentedReinstating the detach/reattach code reduced time to 2.0
Comment #29
somatics CreditAttribution: somatics commentedI don't think this is exclusively a Drupal 8 issue: I am encountering what I think is the same issue described here, but on my Drupal 7 site with a lot of permissions and roles. Disabling javascript in my browser "fixed" it entirely. I've created an issue for it in the Drupal 7 issue queue:
http://drupal.org/node/1214250
Comment #30
webchickMarked the other issue as duplicate, with explanation.
Comment #31
mdupont#437244: Javascript used on permissions page bogs FF3 to a crawl. marked as a duplicate of this issue.
Comment #32
somatics CreditAttribution: somatics commentedI relieved to hear this is a known problem that is being fixed. Thanks so much for all the hard work!
I was at a loss for the past month, and concerned it was just some undiagnosable mystery within our own data. I am having only two other issues that are like this, and now I am wondering if they are a similar system problem -- remember we have a lot of modules installed, so perhaps it is pushing the performance boundaries:
Two other pages timeout on this site:
The /update.php page and the /admin/config page. Keep in mind that on this site, updates can be run from Drush without a hiccup, and all sub pages listed on the config page can be accessed without a single problem -- and the /admin/index page, which lists all the same things that /admin/config does (and more), also displays just fine.
So, I don't want to muddy the waters here, but just in case this was a related javascript issue or something like that, I wanted to mention it. Do those pages have any major javascript activity or anything else that might be caused by this same issue?
Comment #33
lyricnz CreditAttribution: lyricnz commentedRaise a new issue - this one relates specifically to the /admin/people/permissions page.
Comment #34
geerlingguy CreditAttribution: geerlingguy commentedSubscribe, and wow! What a difference the already-applied patch makes. I was lamenting ever visiting the permissions page, with 200+ permissions (we have profile2 set up in a funky way that ends up adding a new set of permissions for each of our site's 'networks', so this number will only increase).
Comment #35
BenK CreditAttribution: BenK commentedSubscribing
Comment #36
fangel CreditAttribution: fangel commentedJust FYI, if you're using LoginToboggan the above patch won't do anything for you, because that module replaces
user.permissions.js
with it's own implementation.I've created the issue #1290730: With large number of permissions /admin/people/permissions becomes unusable on the LoginToboggan issue-queue to try and make sure LoginToboggan updates its code as well (or better yet, find a way to perform its changes without having to reproduce the entire script)
Comment #37
hunmonk CreditAttribution: hunmonk commentedkeeping in line with the commit in #24, i've pushed a matching fix to logintoboggan 7.x-1.x-dev.
Comment #38
sunSo if I get #28 right, then #25 improves browser rendering performance by another 33%.
Re-roll, commit?
Comment #39
lyricnz CreditAttribution: lyricnz commentedWell, it reduced the time from 2.4s to 2.0s (17% improvement).
Comment #40
geerlingguy CreditAttribution: geerlingguy commentedMy test results for patch in #25 (on a huge permissions page, as mentioned above, due to a bunch of Profile2 profile types):
Both times I opened a new incognito window (to be sure JS wasn't cached), flushed all caches on a locally-hosted drupal 7 site, and started the stopwatch at the moment I clicked 'Permissions'.
I repeated the test twice, to make sure my patching was correct (git reset --hard, then git apply -v [patchname]).
Comment #41
xjmHmm, so that sounds to me like the patch is not actually a performance improvement in all cases. (To put it mildly.)
Comment #42
lyricnz CreditAttribution: lyricnz commented@geerlingguy: how many roles * permissions do you have, that this page takes 39 seconds to run the JS?
Comment #43
geerlingguy CreditAttribution: geerlingguy commentedAfter doing a search for
<td class="checkbox">
on the permissions page source, I found 6,540 checkboxes, and I have 6 roles, so there were 1,090 individual permissions to go through.One thing that would speed up my particular site would be hiding the entire 'module-profile2' section (there are over 5,000 checkboxes just in that section of the permissions page, owing to the fact that I have a profile type per 'network' on my site, and there are over 200 networks...). Could I simply hide that section in a hook_page_alter or something like that? Might just do that...
Comment #44
geerlingguy CreditAttribution: geerlingguy commentedOkay, I've added the Filter Permissions module (found it was doing exactly what I was about to do, except in a form_alter), and the results are now (without displaying Profile2 permissions):
Average of three tests each, first two clearing browser caches, third test a simple Command-R refresh (but all three were within .1s).
This is with around 1,200 permissions checkboxes on the page.
Comment #45
astutonetHi friends.
I have this same issue on D7.9 version.
I did a test with the patch in # 25, but didn't work. The error is:
On line 696 of user.admin.inf file, we have this code:
);
As this issue discusses a problem in a different version, I would like to know from friends if the patch above is applicable to my version.
If not, I wonder if there one version of the patch wich applies to the current version of Drupal, because I searched and didn't find.
Thank you.
Comment #46
lyricnz CreditAttribution: lyricnz commented@astutonet:
Drupal 7.9 already includes the patch, it was applied on July 3 #24, and is in Drupal 7.6 or newer.
Don't use the patch in #25 as-written. As commented in #28, it's actually slower (at least in my case), due to it performing DOM manipulations on an attached DOM. I re-added the detach code, and *that* improved performance - not #25 itself. I haven't rerolled the patch with the fix yet.
Comment #47
lyricnz CreditAttribution: lyricnz commentedHere's an updated version of #25 that reinstates the detach from the DOM.
Comment #48
lyricnz CreditAttribution: lyricnz commentedPatch needs fix - detach not working work.
Comment #49
nod_Don't mind me just tagging, too interesting to let this alone :)
Comment #50
AaronBaumanFor anyone coming to this thread for a quick fix, here's the solution i used to get rid of invisible elements and dummy checkboxes altogether:
Of course this doesn't resolve the initial pageload and render time, but it at least makes the page usable after load.
Also, this will make control-f (find on page) work again by removing the invisible elements.
I guess these are meant as an accessibility feature, but they significantly degrade performance on a medium-sized site even on my brand new Mac Mini.
This hack might work for D8 too - i haven't tested.
Comment #51
tierso CreditAttribution: tierso commentedHaving the same problem, and even before reaching this point, I thought the permissions tab was a very lacking (bloated) feature. It might work out fine for a few roles and modules, but once the site "grows" it gets way out of hand.
Why are there no filters (can't even find a single module to add this function, Like the module filter for instance) or even better. Start with a page providing options to select what permissions to view, based on permission category and/or roles, and then display what the admin wishes to see.
Comment #52
pjcdawkins CreditAttribution: pjcdawkins commented@tierso http://drupal.org/project/filter_perms will solve your problem temporarily.
Comment #53
tierso CreditAttribution: tierso commentedAh, my dream coming true. Thank you!
It has everything I could ever wish for.
Comment #54
attiks CreditAttribution: attiks at Attiks commentedFYI: we ran into similar problems a lot, so I've uploaded a cleaned up version of our module https://www.drupal.org/project/dream_permissions. For the moment D7, but can not be hard to port to D8
Comment #55
droplet CreditAttribution: droplet commentedIs it still a problem on modern browsers ?
Comment #56
attiks CreditAttribution: attiks at Attiks commentedIt sure is, on one project the permissions page ended up having 32.000 checkboxes and a loading time of 20+ sec. The dummy checkboxes almost double the number of checkboxes, in our javascript we just disabled the real checkboxes. The only way so solve the loading time is to load less and where possible load it using json.
Comment #57
AaronBaumanConfirming, yes, loading 4+MB of HTML and running inefficient javascript on all of it is still a problem on late-2015 hardware.
Comment #58
catchI've also seen the permissions page get big enough that the POST goes over max_input_vars, which then silently truncates the variables. Adding #1565704: Core interfaces can go over max_input_vars as a related issue.
Comment #62
SKAUGHTComment #68
catchI'm bumping this to major, since the interface goes over both human and PHP limits in terms of form size per #2742371: Drag and drop interfaces can break either browsers or exceed PHP server limits for forms.
Comment #71
scottsawyerThere has got to be a better way of managing a site with a large number of permissions. Certain modules create individual permissions for components created in the UI, such as Entity Browser, ECK, etc. Couple that with 4-6 roles and a site quickly exceeds a reasonable number of inputs. I am hesitant to simply increase my max_input_variables setting, since that could be opening the door to other issues.
I think a reasonable solution would be to have a separate form for each module. They would all be presented in the permissions area, like admin/people/permissions/eck, admin/people/permissions/entity_browser. The current permissions page, /admin/people/permissions, would just be a list of all the modules that have permissions defined. In my mind, this could be done so as to not affect contrib in any way.
Comment #72
yoroy CreditAttribution: yoroy at Roy Scholten commentedSo known "tricks" like separating things out across tabs, vertical tabs or fieldsets do not count because that's just selective display and the sea of checkboxes is still there on one page. It may help people orient themselves better but the technical performance issues would remain with these approaches. (Right?)
Instead, we need to find a pattern that uses selective loading of sets of permissions.
There's potential for a Big Design Exercise in this problem if we want to, but there may be relatively straight forward options we could pursue here as well. For example, something like on the Extend page, listing collapsed permissions per module: expand the section to load the corresponding permissions.
As crucially important as this page is for setting up features correctly across roles, it is important during the relatively smaller time of initial development and configuration of a site. Not quite set it and forget it, but not a high frequency task when a site is up and running. Which is to say, this needs fixing for usability for sure, but it doesn't have to be the most polished, low friction user interface either.
Comment #73
scottsawyerTo be clear, I was not meaning "UI tabs" in the sense that all of the permissions would be loaded in the same page and just displayed behind a tab. I was thinking more that we would create a route for each module's permissions.
Basically, if a module has a {mymodule}.permissions.yml, Drupal creates a route, admin/users/permissions/{mymodule}, and that route would only load the permissions for that module. There would be additional routes for each role. admin/users/permissions/{role}/{mymodule}. Obviously more thinking would need to go into the routes, but I think this would completely solve the problem.
Your suggestion of conditionally loading the permissions might be a reasonable solution as well. You could still run into the problem of trying to save hundreds of inputs, but way less likely.
Another option in the same line of thinking, how about load the permissions for that module in a modal? Then you have to save the permissions for that module before going to the next. The more I think about it, the better that idea sounds.
Comment #74
yoroy CreditAttribution: yoroy at Roy Scholten commentedI got you weren't saying that, I was thinking out loud in my first paragraph there :)
Modal is certainly a promising option from the exisiting toolbox! nice.
Comment #77
Matt BSo I now have over 60 roles on my site. The /admin/people/permissions performance is absolutely dire. I can work around this by going to /admin/people/permissions/[role_machine_name] and that let's me admin permissions by role, but I've lost the ability to see the whole permission set on a page.
I've also discoverred the Drush commands for role management: https://drushcommands.com/drush-9x/role/role:list/
Any update on when there will be a fix for this 9 year old issue? Atleast a read-only view would be handy with links to manage the specific role permissions.
Comment #79
geek-merlinComment #80
quietone CreditAttribution: quietone as a volunteer commentedThere was a commit for this issue in Jul 2011 to both Drupal 7 and Drupal 8 (#24).
It was reopened shortly after to evaluate an alternative approach. In #38 and #39 it seems that new approach is 17% improvement. And supported in #44. So perhaps the way forward here is to open a new issue to evaluate the latest patch.
Checking with #bugsmash before doing that...
Comment #81
quietone CreditAttribution: quietone as a volunteer commentedThe discussion in bugsmash with daravnen, dww, larowlan, lendude, agreed that a new issue is the way to proceed.
Therefor restoring the fixed status on this.
The new issue is #3244564: Improve performance of /admin/people/permissions and moved credit over there.