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
-
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