Recommendations for apps with a large # of user-controlled roles #2444
steven-fox
started this conversation in
Show and tell
Replies: 1 comment 1 reply
-
It could also be optional, through the configuration file to maintain the cache functionality for small apps, and be able to change it if necessary |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
The following is fictional story outlining a real world problem we faced. Your mileage may vary.
Also, no one's perfect, and there's a chance we missed something vital that would completely change our situation. Please feel free to let me know if it's possible to have 20,000+ roles with this package and things still work smoothly.
Use Case | App Requirement
To which the dev team responds, "Ok boss. You got it. There's this permission package we can use that permits the creation and assignment of roles on a per-team basis. Easy peasy."
Design Strategy
Seems easy enough. Install this Spatie package, turn on the teams feature, and every time a new
team
model is created, we also create a set of default roles (each having a default set of permissions from our pool of controllable permissions) for the team, allowing them to modify the permissions associated with each team-role if needed. So we have something like:alarm.create
,alarm.delete
,notification.create
,notification.delete
,billing.manage
,adCampaign.create
,adCampaign.delete
,apiKey.create
,apiKey.delete
,apiKeys.rotate
, ...).admin
,developer
,marketer
, ...) that each have a default set of permissions from our available pool above.team
is created, we create new versions of our 8 default roles, assign theteam_id
accordingly, and relate the new roles with their respective permissions for some sane defaults.This gets implemented and we're off the races. No sweat! Even a junior developer could handle this one!
The Deployment
After some testing on our dev environment, we push to production, seed the new roles/permissions tables with existing data for each team in our system. It was soooo easy.
That is until:
To which the junior dev responds with 😳🤷 and "I'm heading to lunch! Good luck!"
The Oversight
After desperately attempting to figure out what was going on with our system, we realized the following:
This package is set up to cache
permissions.roles
androles
- for every record in yourpermissions
,roles
, androle_has_permissions
tables.Ha! Oops.
role_has_permissions
table had over 250,000 records.When the
PermissionRegistrar
sets/reads the cache, it was loading over a quarter million model instances into and out of cache. Big oops indeed!P.S. I believe the actual checking for permissions could be fast by just using the database layer with these sorts of numbers. I think it's just the cache and the
HasPermissions::hasPermissionViaRole()
method that makes the package slow and unusable for such a business requirement. Here's my thinking, subject to being completely wrong haha: When checking if a person->hasPermissionTo(' ... ')
, all that needs to happen is 1) look up the permission record by identifier (probably < 1ms at the db because of the unique name index, and you could even continue to do this part via a more limited cache implementation), 2) see if the person has a relation to that permission record (which uses themodel_has_permissions
table and performs an index-based lookup in < 1ms), and if necessary, 3) check if the user has a role with that permission (which would still be ultra fast at the database layer because all of this can be done via index lookups). Sure, this might lead to 1 extra db query, but it should be lightening fast. Using the cache the way this package does is great for a small number of roles/permissions, but can quickly get out of hand.Our (Temporary?) Solution
Instead of creating a ton of duplicate, default, roles for each team in our system, we now maintain the default set of 8 roles with a
team_id
ofnull
, allowing us to assign them to any individual user. Then, only if a team actually needs a custom role (either because of a name preference or unique set of permissions), we then create a custom role and update the rest of the tables accordingly. Thus, instead of thousands upon thousands of roles, we have < 100. This solution isn't perfect, however. Once you do have a team with a custom role (so, a role record whereteam_id
is notnull
), you have to be mindful of that and change your role lookups accordingly. It's not as simple asRole::query()->where('team_id', $this->id)->orWhereNull('team_id')
, because you can get back multiple roles with the same name, which will look confusing to a frontend user. You must find any duplicate name/guard combinations and favor the version that has a non-nullteam_id
. Not hard, just a little extra work.The Key Takeaway
Be cautious when using this package, as of version
5.10.1
if you need a lot of roles, which you might think of doing if you want eachteam
in your system to have their own set of roles. Instead, try our strategy of maintaining a global set of roles and only creating new roles if absolutely necessary for a particular team, or use an alternate permission strategy.Beta Was this translation helpful? Give feedback.
All reactions