Я работаю над приложением, в котором пользователь может иметь доступ ко многим формам в различных сценариях. Я пытаюсь построить подход с максимальной производительностью при возврате индекса форм пользователю.
Пользователь может иметь доступ к формам по следующим сценариям:
- Форма собственности
- Команда владеет формой
- Имеет разрешения для группы, которая владеет формой
- Имеет разрешения для команды, которая владеет формой
- Имеет разрешение на форму
Как вы можете видеть, существует 5 возможных способов доступа пользователя к форме. Моя проблема заключается в том, как наиболее эффективно вернуть пользователю массив доступных форм.
Политика формы:
Я попытался получить все формы из модели, а затем отфильтровать формы по политике формы. Похоже, что это проблема производительности, так как на каждой итерации фильтра форма передается через eloquent метод 5 (5), как показано ниже. Чем больше форм в базе данных, тем медленнее становится.
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);
}
Хотя вышеописанный метод работает, это - горлышко бутылки производительности.
Из того, что я вижу, мои следующие варианты:
- Фильтр FormPolicy (текущий подход)
- Запросить все разрешения (5) и объединить в одну коллекцию
- Запрос все идентификаторы для всех разрешений (5), а затем запросить модель формы с использованием идентификаторов в IN () заявление
Мой вопрос:
Какой метод обеспечит лучшую производительность, и есть ли другой вариант, который обеспечит лучшую производительность?
user_form_permission
Таблица , содержащая толькоuser_id
иform_id
. Это сделает разрешение на чтение быстрым, однако обновление разрешений будет сложнее.Ответы:
Я хотел бы сделать SQL-запрос, поскольку он будет работать намного лучше, чем PHP
Что-то вроде этого:
Из головы и непроверенных это должно получить все формы, которые принадлежат пользователю, его группам и этим командам.
Однако он не рассматривает разрешения пользовательских форм просмотра в группах и командах.
Я не уверен, как у вас настроена авторизация для этого, поэтому вам нужно будет изменить запрос для этого и любых различий в структуре вашей БД.
источник
OR
пункты, которые, я подозреваю, будут медленными. Так что удар по этому запросу будет безумным, я верю.Короткий ответ
Третий вариант:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Длинный ответ
С одной стороны, (почти) все, что вы можете сделать в коде, лучше с точки зрения производительности, чем в запросах.
С другой стороны, получение большего количества данных из базы данных, чем необходимо, уже было бы слишком большим объемом данных (использование ОЗУ и т. Д.).
С моей точки зрения, вам нужно что-то промежуточное, и только вы будете знать, где будет баланс, в зависимости от чисел.
Я бы предложил выполнить несколько запросов, последний вариант, который вы предложили (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Вы можете попробовать три предложенных вами варианта и отслеживать производительность, используя какой-либо инструмент для многократного выполнения запроса, но я на 99% уверен, что последний из них даст вам наилучшую производительность.
Это также может сильно измениться, в зависимости от того, какую базу данных вы используете, но если мы говорим, например, о MySQL; В очень большом запросе будет использоваться больше ресурсов базы данных, который не только будет тратить больше времени, чем простые запросы, но также будет блокировать таблицу от записи, и это может привести к ошибкам взаимоблокировки (если вы не используете подчиненный сервер).
С другой стороны, если количество идентификаторов форм очень велико, вы можете иметь ошибки для слишком большого количества заполнителей, поэтому вы можете разделить запросы на группы, скажем, 500 идентификаторов (это зависит от предела по размеру, а не по количеству привязок) и объединить результаты в памяти. Даже если вы не получите ошибку базы данных, вы также можете увидеть большую разницу в производительности (я все еще говорю о MySQL).
Реализация
Я предполагаю, что это схема базы данных:
Так допустимо было бы уже настроенные полиморфные отношения .
Следовательно, отношения будут:
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'
Упрощенная версия:
Подробная версия:
Использованные ресурсы:
Производительность базы данных:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, в памяти, производительность:
array_values(array_unique())
чтобы не повторять идентификаторы.$teamIds
,$groupIds
,$formIds
)Плюсы и минусы
ПЛЮСЫ:
МИНУСЫ:
Как измерить производительность
Некоторые подсказки о том, как измерить производительность?
Некоторые интересные инструменты профилирования:
источник
array_merge()
иarray_unique()
куча идентификаторов действительно замедлить ваш процесс.array_unique()
быстрее, чемGROUP BY
/SELECT DISTINCT
оператор.Почему вы не можете просто запросить нужные вам формы, вместо того, чтобы выполнять
Form::all()
и затем связыватьfilter()
функцию после нее?Вот так:
Так что да, это делает несколько запросов:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
Однако плюсом является то, что вам больше не нужно использовать политику , поскольку вы знаете, что все формы в
$forms
параметре разрешены для пользователя.Таким образом, это решение будет работать для любого количества форм в базе данных.
Если вы хотите, чтобы это было еще быстрее, вы должны создать собственный запрос, используя фасад БД, что-то вроде:
Ваш реальный запрос намного больше, так как у вас так много отношений.
Основное улучшение производительности здесь связано с тем, что тяжелая работа (подзапрос) полностью обходит логику модели Eloquent. Затем все, что осталось сделать, это передать список идентификаторов в
whereIn
функцию для получения спискаForm
объектов.источник
Я полагаю, что вы можете использовать Lazy Collections для этого (Laravel 6.x) и стремиться загрузить отношения до того, как они будут доступны.
источник