Closed derekmd closed 7 years ago
I have a question for you! Why is it that your redis cache does not hit all the time after the first request?
It is hit.
The problem is a serialized Eloquent Collection
object in the Redis store is a 1.8mb string. That is fetched from Redis by each request. With sufficient traffic, that becomes its own bottleneck as it affects Redis hits (even outside permissions) for all other requests.
I managed to lower the role/permission Collection
memory footprint but 1.2mb is still excessive. I'll have to rethink the permission scheme as one-to-one Permission
-to-route name isn't feasible for hundreds of routes.
Hi, I'm going to close this as your question is about an edge case problem where most users won't run into. If you find a good solution for this, I'd appreciate you posting it here, as it may prove helpful for users hitting the same problem.
My quick fix has been to lower the memory footprint of the serialized Collection
.
Remove timestamps from the permissions
and roles
tables.
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Migrations\Migration;
class AlterPermissionsDropTimestamps extends Migration
{
public function up()
{
$config = config('laravel-permission.table_names');
Schema::table($config['roles'], function ($table) {
$table->dropColumn('created_at', 'updated_at');
});
Schema::table($config['permissions'], function ($table) {
$table->dropColumn('created_at', 'updated_at');
});
}
}
Whitelist superadmin-only routes (as I'm doing one-to-one mapping with permissions
) and remove those routes from the permissions
table. In the User
model:
use HasRoles {
hasPermissionTo as spatieHasPermissionTo;
}
public function hasPermissionTo($permission)
{
if ($this->isAdmin()) {
return true;
}
try {
return $this->spatieHasPermissionTo($permission);
} catch (PermissionDoesNotExist $e) {
return false;
}
}
That will only hit the roles
table without touching the cache.
Eventually I want to:
PermissionRegistrar@registrarPermissions()
conditionally as it's not needed for certain request URI namespaces like /api/
.admin.resource.*
rather than each admin.resource.index
, admin.resource.create
, admin.resource.destroy
, etc. Unfortunately these controllers have been sloppily done by not using RESTful resource controller verbs (for both method names and route name aliases) so it stands a lengthy task to get right. Consider it another reason to DHH a Laravel project's controllers from the outset.
An issue has popped with this package reading large serialized binary from Redis for every request, making the Redis instance a bottleneck when a sudden blast of traffic comes in.
From looking at the service provider, the stack looks something like:
PermissionServiceProvider@boot()
PermissionRegistrar@getPermissions()
-> define gate policiesCache::rememberForever()
. When the cache hit misses:Illuminate\Cache\RedisStore@serialize()
serialize(app(Permission::class)->with('roles')->get()))
So even when the database query is avoided, the cache will unserialize the
Collection
ofPermission
models (with eager-loadedRole
items on each instance.)For over 220 permissions on production:
That's a fairly sizeable payload to fetch from Redis per request, making database query avoidance redundant as volume and frequency of traffic increases.
I have a feeling this app has more granular permissions than most so this a statie/laravel-permission use case outlier.
Does anyone have ideas how to improve performance?
boot()
callingGuard::define()
depending on a request path or authorized user? Without the package being a deferred service, I don't see that being possible. e.g.,Auth::guard('something')->can()
Collection
instances. Although in that case it still re-fetches some from the database.