Ich arbeite an einer Anwendung, bei der ein Benutzer über viele verschiedene Szenarien auf viele Formulare zugreifen kann. Ich versuche, den Ansatz mit der besten Leistung zu erstellen, wenn ein Index von Formularen an den Benutzer zurückgegeben wird.
Ein Benutzer kann über die folgenden Szenarien auf Formulare zugreifen:
- Besitzt Formular
- Team besitzt Form
- Hat Berechtigungen für eine Gruppe, die ein Formular besitzt
- Hat Berechtigungen für ein Team, das ein Formular besitzt
- Hat die Erlaubnis zu einem Formular
Wie Sie sehen, gibt es 5 Möglichkeiten, wie der Benutzer auf ein Formular zugreifen kann. Mein Problem ist, wie ich ein Array der zugänglichen Formulare am effizientesten an den Benutzer zurückgeben kann.
Formularrichtlinie:
Ich habe versucht, alle Formulare aus dem Modell abzurufen und die Formulare dann nach der Formularrichtlinie zu filtern. Dies scheint ein Leistungsproblem zu sein, da bei jeder Filteriteration das Formular fünfmal durch eine beredte Methode mit enthält () geleitet wird, wie unten gezeigt. Je mehr Formulare in der Datenbank vorhanden sind, desto langsamer wird dies.
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);
}
Obwohl die obige Methode funktioniert, handelt es sich um einen Leistungsflaschenhals.
Nach dem, was ich sehen kann, sind meine folgenden Optionen:
- FormPolicy-Filter (aktueller Ansatz)
- Fragen Sie alle Berechtigungen ab (5) und führen Sie sie zu einer einzigen Sammlung zusammen
- Fragen Sie alle Bezeichner nach allen Berechtigungen ab (5) und fragen Sie dann das Formularmodell mithilfe der Bezeichner in einer IN () -Anweisung ab
Meine Frage:
Welche Methode bietet die beste Leistung und gibt es eine andere Option, die eine bessere Leistung bietet?
user_form_permission
Tabelle enthält nur dasuser_id
und dasform_id
. Dadurch werden Leseberechtigungen zum Kinderspiel, das Aktualisieren von Berechtigungen wird jedoch schwieriger.Antworten:
Ich würde versuchen, eine SQL-Abfrage durchzuführen, da dies viel besser als PHP ist
Etwas wie das:
Von oben und ungetestet sollten Sie alle Formulare erhalten, die dem Benutzer, seinen Gruppen und diesen Teams gehören.
Die Berechtigungen der Benutzeransichtsformulare in Gruppen und Teams werden jedoch nicht berücksichtigt.
Ich bin nicht sicher, wie Sie Ihre Authentifizierung dafür eingerichtet haben, und daher müssten Sie die Abfrage für diese und alle Unterschiede in Ihrer DB-Struktur ändern.
quelle
OR
Klauseln, von denen ich vermute, dass sie langsam sein werden. Ich glaube, es wird verrückt sein, dies bei jeder Anfrage zu tun.Kurze Antwort
Die dritte Option:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Lange Antwort
Einerseits ist (fast) alles, was Sie in Code tun können, in Bezug auf die Leistung besser als in Abfragen.
Andererseits würde das Abrufen von mehr Daten aus der Datenbank als erforderlich bereits zu viele Daten sein (RAM-Nutzung usw.).
Aus meiner Sicht brauchen Sie etwas dazwischen, und nur Sie werden wissen, wo das Gleichgewicht sein würde, abhängig von den Zahlen.
Ich würde vorschlagen, mehrere Abfragen auszuführen, die letzte Option, die Sie vorgeschlagen haben (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Sie können die drei von Ihnen vorgeschlagenen Optionen ausprobieren und die Leistung überwachen, indem Sie die Abfrage mit einem Tool mehrmals ausführen. Ich bin jedoch zu 99% sicher, dass die letzte Option die beste Leistung bietet.
Dies kann sich auch stark ändern, je nachdem, welche Datenbank Sie verwenden, aber wenn wir zum Beispiel über MySQL sprechen; In einer sehr großen Abfrage werden mehr Datenbankressourcen verwendet, die nicht nur mehr Zeit als einfache Abfragen aufwenden, sondern auch die Tabelle vor Schreibvorgängen schützen. Dies kann zu Deadlock-Fehlern führen (es sei denn, Sie verwenden einen Slave-Server).
Wenn andererseits die Anzahl der Formular-IDs sehr groß ist, können Fehler für zu viele Platzhalter auftreten. Daher möchten Sie die Abfragen möglicherweise in Gruppen von beispielsweise 500 IDs aufteilen (dies hängt stark von der Grenze ab ist in der Größe, nicht in der Anzahl der Bindungen) und führen die Ergebnisse im Speicher zusammen. Selbst wenn Sie keinen Datenbankfehler erhalten, können Sie auch einen großen Leistungsunterschied feststellen (ich spreche immer noch über MySQL).
Implementierung
Ich gehe davon aus, dass dies das Datenbankschema ist:
Zulässig wäre also eine bereits konfigurierte polymorphe Beziehung .
Daher wären die Beziehungen:
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 vereinfachen:
Detaillierte Version:
Verwendete Ressourcen:
Datenbankleistung:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, im Speicher, Leistung:
array_values(array_unique())
um zu vermeiden, dass die IDs wiederholt werden.$teamIds
,$groupIds
,$formIds
)Vor-und Nachteile
PROS:
Nachteile:
So messen Sie die Leistung
Einige Hinweise zur Messung der Leistung?
Einige interessante Profiling-Tools:
quelle
array_merge()
undarray_unique()
eine Reihe von IDs dies tun würden verlangsamen Sie wirklich Ihren Prozess.array_unique()
schneller ist als eineGROUP BY
/SELECT DISTINCT
-Anweisung.Warum können Sie nicht einfach die Formulare abfragen, die Sie benötigen, anstatt a zu tun
Form::all()
und dann zu verketten?filter()
Funktion ?Wie so:
Also ja, das macht ein paar Fragen:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
Der Vorteil ist jedoch, dass Sie die Richtlinie nicht mehr verwenden müssen , da Sie wissen, dass alle Formulare im
$forms
Parameter für den Benutzer zulässig sind.Diese Lösung funktioniert also für jede Anzahl von Formularen, die Sie in der Datenbank haben.
Wenn Sie möchten, dass es noch schneller geht, sollten Sie mithilfe der DB-Fassade eine benutzerdefinierte Abfrage erstellen.
Ihre eigentliche Abfrage ist viel größer, da Sie so viele Beziehungen haben.
Die Hauptverbesserung der Leistung ergibt sich aus der Tatsache, dass die schwere Arbeit (die Unterabfrage) die eloquente Modelllogik vollständig umgeht. Dann müssen Sie nur noch die Liste der IDs an die
whereIn
Funktion übergeben, um Ihre Liste derForm
Objekte abzurufen .quelle
Ich glaube, Sie können Lazy Collections dafür verwenden (Laravel 6.x) und die Beziehungen eifrig laden, bevor auf sie zugegriffen wird.
quelle