Pracuję nad aplikacją, w której użytkownik może mieć dostęp do wielu formularzy poprzez wiele różnych scenariuszy. Usiłuję zbudować to podejście z najlepszą wydajnością, zwracając użytkownikowi indeks formularzy.
Użytkownik może mieć dostęp do formularzy za pomocą następujących scenariuszy:
- Forma właściciela
- Zespół jest właścicielem formularza
- Ma uprawnienia do grupy, która jest właścicielem formularza
- Ma uprawnienia do zespołu, który jest właścicielem formularza
- Ma pozwolenie na formularz
Jak widać, istnieje 5 możliwych sposobów dostępu do formularza przez użytkownika. Mój problem polega na tym, jak najbardziej wydajnie zwrócić użytkownikowi tablicę dostępnych formularzy.
Zasady dotyczące formularza:
Próbowałem pobrać wszystkie formularze z modelu, a następnie filtrować formularze według zasad formularzy. Wydaje się, że jest to problem z wydajnością, ponieważ przy każdej iteracji filtra formularz jest przepuszczany przez elokwentną metodę zawiera () 5 razy, jak pokazano poniżej. Im więcej formularzy w bazie danych, tym wolniej.
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);
}
Chociaż powyższa metoda działa, jest to szyjka butelki wydajności.
Z tego, co widzę, są następujące moje opcje:
- Filtr FormPolicy (obecne podejście)
- Zapytaj o wszystkie uprawnienia (5) i połącz w jedną kolekcję
- Zapytanie wszystkie identyfikatory dla wszystkich uprawnień (5), a następnie kwerendy Wzór formularza przy użyciu identyfikatorów w IN () oświadczenie
Moje pytanie:
Która metoda zapewni najlepszą wydajność i czy jest jakaś inna opcja, która zapewni lepszą wydajność?
user_form_permission
Tabela zawierająca tylkouser_id
iform_id
. Sprawi to, że uprawnienia do odczytu będą proste, jednak uprawnienia do aktualizacji będą trudniejsze.Odpowiedzi:
Chciałbym wykonać zapytanie SQL, ponieważ będzie ono działać znacznie lepiej niż php
Coś takiego:
Z góry mojej głowy i niesprawdzone to powinno dostarczyć ci wszystkich formularzy, które są własnością użytkownika, jego grup i zespołów.
Nie uwzględnia jednak uprawnień formularzy przeglądania użytkowników w grupach i zespołach.
Nie jestem pewien, jak skonfigurowałeś do tego autoryzację, więc musisz zmodyfikować zapytanie o to i jakąkolwiek różnicę w strukturze DB.
źródło
OR
klauzule, które, jak podejrzewam, będą wolne. Wierzę, że trafienie w to na każde żądanie będzie szalone.Krótka odpowiedź
Trzecia opcja:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Długa odpowiedź
Z jednej strony (prawie) wszystko, co można zrobić w kodzie, jest lepsze pod względem wydajności niż w przypadku zapytań.
Z drugiej strony, pobieranie większej ilości danych z bazy danych, niż to konieczne, byłoby już zbyt dużą ilością danych (użycie pamięci RAM i tak dalej).
Z mojej perspektywy potrzebujesz czegoś pomiędzy, a tylko będziesz wiedział, gdzie będzie równowaga, w zależności od liczb.
Sugerowałbym uruchomienie kilku zapytań, ostatnia zaproponowana opcja (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Możesz wypróbować trzy zaproponowane opcje i monitorować wydajność, używając narzędzia do wielokrotnego uruchamiania zapytania, ale jestem 99% pewien, że ostatnia zapewni najlepszą wydajność.
To również może się wiele zmienić, w zależności od używanej bazy danych, ale jeśli mówimy na przykład o MySQL; W bardzo dużym zapytaniu zużyłoby więcej zasobów bazy danych, co nie tylko poświęci więcej czasu niż proste zapytania, ale także zablokuje tabelę przed zapisem, co może powodować błędy zakleszczenia (chyba że użyjesz serwera podrzędnego).
Z drugiej strony, jeśli liczba identyfikatorów formularzy jest bardzo duża, możesz mieć błędy dla zbyt wielu symboli zastępczych, więc możesz chcieć podzielić zapytania na grupy, powiedzmy, 500 identyfikatorów (zależy to bardzo od limitu ma rozmiar, a nie liczbę powiązań) i scal wyniki w pamięci. Nawet jeśli nie dostaniesz błędu bazy danych, możesz również zauważyć dużą różnicę w wydajności (wciąż mówię o MySQL).
Realizacja
Zakładam, że jest to schemat bazy danych:
Tak więc dopuszczalna byłaby już skonfigurowana relacja polimorficzna .
Dlatego relacje byłyby następujące:
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'
Uprość wersję:
Wersja szczegółowa:
Wykorzystane zasoby:
Wydajność bazy danych:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, w pamięci, wydajność:
array_values(array_unique())
aby uniknąć powtarzania identyfikatorów.$teamIds
,$groupIds
,$formIds
)Plusy i minusy
Plusy:
CONS:
Jak mierzyć wydajność
Kilka wskazówek na temat pomiaru wydajności?
Kilka interesujących narzędzi profilujących:
źródło
array_merge()
iarray_unique()
zbiór identyfikatorów naprawdę spowalniają twój proces.array_unique()
jest szybsze niż instrukcjaGROUP BY
/SELECT DISTINCT
.Dlaczego nie możesz po prostu zapytać o Formularze, których potrzebujesz, zamiast robić,
Form::all()
a następnie tworzyć łańcuchyfilter()
funkcję po niej?Tak jak:
Tak, to robi kilka zapytań:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
Jednak zaletą jest to, że nie musisz już korzystać z zasad , ponieważ wiesz, że wszystkie formularze w
$forms
parametrze są dozwolone dla użytkownika.To rozwiązanie będzie działać dla dowolnej liczby formularzy w bazie danych.
Jeśli chcesz, aby był jeszcze szybszy, powinieneś utworzyć niestandardowe zapytanie przy użyciu fasady DB, coś w stylu:
Twoje aktualne zapytanie jest znacznie większe, ponieważ masz tak wiele relacji.
Główna poprawa wydajności wynika tutaj z faktu, że ciężka praca (podzapytanie) całkowicie omija logikę modelu Eloquent. Następnie wystarczy przekazać listę identyfikatorów do
whereIn
funkcji, aby pobrać listęForm
obiektów.źródło
Wierzę, że możesz do tego użyć Lazy Kolekcje (Laravel 6.x) i chętnie załadować relacje, zanim zostaną one udostępnione.
źródło