Je travaille sur une application où un utilisateur peut avoir accès à de nombreux formulaires à travers de nombreux scénarios différents. J'essaie de construire l'approche avec les meilleures performances lors du retour d'un index de formulaires à l'utilisateur.
Un utilisateur peut avoir accès aux formulaires via les scénarios suivants:
- Formulaire propriétaire
- L'équipe possède le formulaire
- Possède des autorisations sur un groupe propriétaire d'un formulaire
- A des autorisations pour une équipe qui possède un formulaire
- A l'autorisation d'un formulaire
Comme vous pouvez le voir, l'utilisateur peut accéder à un formulaire de 5 manières différentes. Mon problème est de savoir comment renvoyer le plus efficacement possible un tableau des formulaires accessibles à l'utilisateur.
Politique de formulaire:
J'ai essayé d'obtenir tous les formulaires du modèle, puis de filtrer les formulaires par la stratégie de formulaire. Cela semble être un problème de performances car à chaque itération de filtre, le formulaire est passé 5 fois par une méthode éloquente contains () comme indiqué ci-dessous. Plus il y a de formulaires dans la base de données, cela ralentit.
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);
}
Bien que la méthode ci-dessus fonctionne, il s'agit d'un col de bouteille performant.
D'après ce que je peux voir, mes options suivantes sont:
- Filtre FormPolicy (approche actuelle)
- Recherchez toutes les autorisations (5) et fusionnez en une seule collection
- Recherchez tous les identifiants pour toutes les autorisations (5), puis interrogez le modèle de formulaire à l'aide des identifiants dans une instruction IN ()
Ma question:
Quelle méthode fournirait les meilleures performances et existe-t-il une autre option qui fournirait de meilleures performances?
user_form_permission
tableau contenant uniquement leuser_id
et leform_id
. Cela rendra les autorisations de lecture un jeu d'enfant, mais la mise à jour des autorisations sera plus difficile.Réponses:
Je chercherais à faire une requête SQL car cela va fonctionner beaucoup mieux que php
Quelque chose comme ça:
Du haut de ma tête et non testé, cela devrait vous procurer tous les formulaires qui appartiennent à l'utilisateur, à ses groupes et à ces équipes.
Il ne regarde cependant pas les autorisations des formulaires de vue utilisateur dans les groupes et les équipes.
Je ne sais pas comment vous avez configuré votre authentification pour cela et vous devrez donc modifier la requête pour cela et toute différence dans votre structure de base de données.
la source
OR
clauses qui, je suppose, vont être lentes. Donc, frapper cela à chaque demande sera fou, je crois.Réponse courte
La troisième option:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Longue réponse
D'une part, (presque) tout ce que vous pouvez faire dans le code est meilleur en termes de performances que dans les requêtes.
D'un autre côté, obtenir plus de données de la base de données que nécessaire serait déjà trop de données (utilisation de la RAM, etc.).
De mon point de vue, vous avez besoin de quelque chose entre les deux, et vous seul saurez où serait l'équilibre, selon les chiffres.
Je suggérerais d'exécuter plusieurs requêtes, la dernière option que vous avez proposée (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Vous pouvez essayer les trois options que vous avez proposées et surveiller les performances, en utilisant un outil pour exécuter la requête plusieurs fois, mais je suis sûr à 99% que la dernière vous donnera les meilleures performances.
Cela peut également changer beaucoup, selon la base de données que vous utilisez, mais si nous parlons de MySQL, par exemple; Dans Une très grande requête utiliserait plus de ressources de base de données, ce qui non seulement passera plus de temps que les requêtes simples, mais verrouillera également la table des écritures, ce qui peut produire des erreurs de blocage (sauf si vous utilisez un serveur esclave).
D'un autre côté, si le nombre d'identifiants de formulaires est très grand, vous pouvez avoir des erreurs pour trop d'espaces réservés, vous pouvez donc vouloir découper les requêtes en groupes de, disons, 500 identifiants (cela dépend beaucoup, car la limite est en taille, pas en nombre de liaisons) et fusionner les résultats en mémoire. Même si vous n'obtenez pas d'erreur de base de données, vous pouvez également voir une grande différence de performances (je parle toujours de MySQL).
la mise en oeuvre
Je suppose que c'est le schéma de base de données:
Une relation polymorphe déjà configurée serait donc admissible .
Par conséquent, les relations seraient:
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'
Version simplifiée:
Version détaillée:
Ressources utilisées:
Performances de la base de données:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, en mémoire, performances:
array_values(array_unique())
pour éviter de répéter les identifiants.$teamIds
,$groupIds
,$formIds
)Avantages et inconvénients
AVANTAGES:
LES INCONVÉNIENTS:
Comment mesurer les performances
Quelques indices sur la façon de mesurer la performance?
Quelques outils de profilage intéressants:
la source
array_merge()
etarray_unique()
un tas d'identifiants, serait vraiment ralentir votre processus.array_unique()
est plus rapide qu'une instructionGROUP BY
/SELECT DISTINCT
.Pourquoi ne pouvez-vous pas simplement interroger les formulaires dont vous avez besoin, au lieu de le faire
Form::all()
, puis d'enchaîner unefilter()
fonction après?Ainsi:
Alors oui, cela fait quelques requêtes:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
Cependant, le côté pro est que vous n'avez plus besoin d'utiliser la stratégie , car vous savez que tous les formulaires du
$forms
paramètre sont autorisés pour l'utilisateur.Cette solution fonctionnera donc pour n'importe quelle quantité de formulaires que vous avez dans la base de données.
Si vous voulez que ce soit encore plus rapide, vous devez créer une requête personnalisée en utilisant la façade DB, quelque chose comme:
Votre requête réelle est beaucoup plus importante car vous avez tant de relations.
La principale amélioration des performances vient du fait que le travail lourd (la sous-requête) contourne complètement la logique du modèle Eloquent. Il ne reste alors plus qu'à passer la liste des identifiants dans la
whereIn
fonction pour récupérer votre liste d'Form
objets.la source
Je crois que vous pouvez utiliser Lazy Collections pour cela (Laravel 6.x) et charger les relations avant qu'elles ne soient accessibles.
la source