Estoy trabajando en una aplicación donde un usuario puede tener acceso a muchos formularios a través de diferentes escenarios. Estoy tratando de construir el enfoque con el mejor rendimiento al devolver un índice de formularios al usuario.
Un usuario puede tener acceso a los formularios a través de los siguientes escenarios:
- Posee formulario
- El equipo posee el formulario
- Tiene permisos para un grupo que posee un formulario
- Tiene permisos para un equipo que posee un formulario
- Tiene permiso para un formulario
Como puede ver, hay 5 formas posibles en que el usuario puede acceder a un formulario. Mi problema es cómo puedo devolver de manera más eficiente una matriz de formularios accesibles al usuario.
Política de formulario:
He intentado obtener todos los formularios del modelo y luego filtrar los formularios por la política de formularios. Esto parece ser un problema de rendimiento ya que en cada iteración de filtro, el formulario se pasa a través de un método elocuente contiene () 5 veces como se muestra a continuación. Cuantos más formularios haya en la base de datos, esto se vuelve más lento.
FormController@index
public function index(Request $request)
{
$forms = Form::all()
->filter(function($form) use ($request) {
return $request->user()->can('view',$form);
});
}
FormPolicy@view
public function view(User $user, Form $form)
{
return $user->forms->contains($form) ||
$user->team->forms->contains($form) ||
$user->permissible->groups->forms($contains);
}
Aunque el método anterior funciona, es un cuello de botella de alto rendimiento.
Por lo que puedo ver, mis siguientes opciones son:
- Filtro FormPolicy (enfoque actual)
- Consultar todos los permisos (5) y combinarlos en una sola colección
- Consulte todos los identificadores para todos los permisos (5), luego consulte el modelo de formulario utilizando los identificadores en una instrucción IN ()
Mi pregunta:
¿Qué método proporcionaría el mejor rendimiento y hay alguna otra opción que ofrezca un mejor rendimiento?
user_form_permission
tabla que contiene solo eluser_id
y elform_id
. Esto hará que los permisos de lectura sean muy sencillos, sin embargo, actualizar los permisos será más difícil.Respuestas:
Me gustaría hacer una consulta SQL, ya que funcionará mucho mejor que php
Algo como esto:
Desde lo alto de mi cabeza y sin probar esto, debería obtener todos los formularios que son propiedad del usuario, sus grupos y estos equipos.
Sin embargo, no analiza los permisos de los formularios de vista de usuario en grupos y equipos.
No estoy seguro de cómo configurar su autenticación para esto, por lo que necesitaría modificar la consulta para esto y cualquier diferencia en su estructura de base de datos.
fuente
OR
cláusulas, que sospecho que van a ser lentas. Así que golpear esto en cada solicitud será una locura, creo.Respuesta corta
La tercera opción:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Respuesta larga
Por un lado, (casi) todo lo que puede hacer en código, es mejor en términos de rendimiento, que hacerlo en consultas.
Por otro lado, obtener más datos de la base de datos de los necesarios ya sería demasiados datos (uso de RAM, etc.).
Desde mi punto de vista, necesitas algo intermedio, y solo tú sabrás dónde estaría el saldo, dependiendo de los números.
Sugeriría ejecutar varias consultas, la última opción que propuso (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Puede probar las tres opciones que propuso y controlar el rendimiento, utilizando alguna herramienta para ejecutar la consulta varias veces, pero estoy 99% seguro de que la última le dará el mejor rendimiento.
Esto también puede cambiar mucho, dependiendo de qué base de datos esté usando, pero si estamos hablando de MySQL, por ejemplo; En una consulta muy grande, se usarían más recursos de la base de datos, lo que no solo pasará más tiempo que las consultas simples, sino que también bloqueará la tabla de las escrituras, y esto puede producir errores de punto muerto (a menos que use un servidor esclavo).
Por otro lado, si el número de identificadores de formularios es muy grande, puede tener errores para demasiados marcadores de posición, por lo que es posible que desee agrupar las consultas en grupos de, digamos, 500 identificadores (esto depende mucho, ya que el límite está en tamaño, no en número de enlaces), y combina los resultados en la memoria. Incluso si no recibe un error en la base de datos, también puede ver una gran diferencia en el rendimiento (todavía estoy hablando de MySQL).
Implementación
Asumiré que este es el esquema de la base de datos:
Tan permisible sería una relación polimórfica ya configurada .
Por lo tanto, las relaciones serían:
users.id <-> form.user_id
users.team_id <-> form.team_id
permissible.user_id <-> users.id && permissible.permissible_type = 'App\Team'
permissible.user_id <-> users.id && permissible.permissible_type = 'App\Group'
permissible.user_id <-> users.id && permissible.permissible_type = 'App\From'
Versión simplificada:
Versión detallada:
Recursos utilizados:
Rendimiento de la base de datos:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, en memoria, rendimiento:
array_values(array_unique())
para evitar repetir los identificadores.$teamIds
,$groupIds
,$formIds
)Pros y contras
PROS:
CONTRAS:
Cómo medir el desempeño
¿Algunas pistas sobre cómo medir el rendimiento?
Algunas herramientas de perfilado interesantes:
fuente
array_merge()
yarray_unique()
un montón de identificadores, Realmente ralentiza tu proceso.array_unique()
sea más rápido que una declaraciónGROUP BY
/SELECT DISTINCT
.¿Por qué no puede simplemente consultar los formularios que necesita, en lugar de hacerlo
Form::all()
y luego encadenar unafilter()
función después?Al igual que:
Entonces sí, esto hace algunas consultas:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
Sin embargo, el lado profesional es que ya no necesita usar la política , ya que sabe que todos los formularios en el
$forms
parámetro están permitidos para el usuario.Entonces, esta solución funcionará para cualquier cantidad de formularios que tenga en la base de datos.
Si desea que sea aún más rápido, debe crear una consulta personalizada utilizando la fachada DB, algo así como:
Su consulta real es mucho más grande ya que tiene muchas relaciones.
La mejora principal del rendimiento aquí proviene del hecho de que el trabajo pesado (la subconsulta) omite por completo la lógica del modelo Eloquent. Entonces todo lo que queda por hacer es pasar la lista de identificadores a la
whereIn
función para recuperar su lista deForm
objetos.fuente
Creo que puede usar Lazy Collections para eso (Laravel 6.x) y cargar las relaciones antes de acceder.
fuente