Estou trabalhando em um aplicativo em que um usuário pode ter acesso a muitos formulários através de vários cenários diferentes. Estou tentando criar a abordagem com o melhor desempenho ao retornar um índice de formulários para o usuário.
Um usuário pode ter acesso aos formulários através dos seguintes cenários:
- Possui Formulário
- Equipe possui Formulário
- Tem permissões para um grupo que possui um formulário
- Tem permissões para uma equipe que possui um formulário
- Tem permissão para um formulário
Como você pode ver, existem 5 maneiras possíveis de o usuário acessar um formulário. Meu problema é como retornar com mais eficiência uma matriz de formulários acessíveis ao usuário.
Política do formulário:
Tentei obter todos os formulários do modelo e filtrar os formulários pela política de formulários. Isso parece ser um problema de desempenho, pois em cada iteração de filtro o formulário é passado por um método eloqüente contains () 5 vezes, conforme mostrado abaixo. Quanto mais formulários no banco de dados, isso fica mais 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);
}
Embora o método acima funcione, é um gargalo de desempenho.
Pelo que posso ver, minhas seguintes opções são:
- Filtro FormPolicy (abordagem atual)
- Consultar todas as permissões (5) e mesclar em coleção única
- Consulte todos os identificadores para obter todas as permissões (5) e, em seguida, consulte o modelo de formulário usando os identificadores em uma instrução IN ()
Minha pergunta:
Qual método forneceria o melhor desempenho e há outra opção que proporcionaria um melhor desempenho?
user_form_permission
tabela contendo apenas oeuser_id
oform_id
. Isso facilitará bastante as permissões de leitura, mas a atualização das permissões será mais difícil.Respostas:
Eu gostaria de fazer uma consulta SQL, pois isso terá um desempenho muito melhor que o php
Algo assim:
Do alto da minha cabeça e não testado, você deve obter todos os formulários que pertencem ao usuário, aos grupos dele e às equipes.
No entanto, ele não analisa as permissões dos formulários de exibição do usuário em grupos e equipes.
Não tenho certeza de como você configurou sua autenticação para isso e, portanto, seria necessário modificar a consulta para essa e qualquer diferença na sua estrutura de banco de dados.
fonte
OR
cláusulas, as quais suspeito que serão lentas. Então, acertar isso em cada solicitação será insano, acredito.Resposta curta
A terceira opção:
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
Resposta longa
Por um lado, (quase) tudo o que você pode fazer no código é melhor em termos de desempenho do que nas consultas.
Por outro lado, obter mais dados do banco de dados do que o necessário já seria um excesso de dados (uso de RAM e assim por diante).
Na minha perspectiva, você precisa de algo intermediário, e só você saberá onde estaria o equilíbrio, dependendo dos números.
Sugiro executar várias consultas, a última opção que você propôs (
Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
):array_unique($ids)
Você pode tentar as três opções propostas e monitorar o desempenho, usando alguma ferramenta para executar a consulta várias vezes, mas tenho 99% de certeza de que a última fornecerá o melhor desempenho.
Isso também pode mudar bastante, dependendo de qual banco de dados você estiver usando, mas se estamos falando sobre MySQL, por exemplo; Em Uma consulta muito grande usaria mais recursos do banco de dados, que não apenas gastariam mais tempo do que consultas simples, mas também bloqueariam a tabela de gravações, e isso pode produzir erros de conflito (a menos que você use um servidor escravo).
Por outro lado, se o número de IDs de formulários for muito grande, você poderá ter erros para muitos espaços reservados, portanto, você pode agrupar as consultas em grupos de, digamos, 500 IDs (isso depende muito, pois o limite tem tamanho, não número de ligações) e mescla os resultados na memória. Mesmo se você não receber um erro no banco de dados, também poderá ver uma grande diferença no desempenho (ainda estou falando do MySQL).
Implementação
Vou assumir que este é o esquema do banco de dados:
Tão admissível seria uma relação polimórfica já configurada .
Portanto, as relações seriam:
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'
Simplifique a versão:
Versão detalhada:
Recursos utilizados:
Desempenho do banco de dados:
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...)
.PHP, na memória, desempenho:
array_values(array_unique())
para evitar repetir os IDs.$teamIds
,$groupIds
,$formIds
)Prós e contras
PROS:
CONTRAS:
Como medir o desempenho
Algumas dicas sobre como medir o desempenho?
Algumas ferramentas interessantes de criação de perfil:
fonte
array_merge()
earray_unique()
um monte de ids realmente desacelere seu processo.array_unique()
é mais rápida que uma declaraçãoGROUP BY
/SELECT DISTINCT
.Por que você não pode simplesmente consultar os formulários necessários, em vez de fazer
Form::all()
e encadear umfilter()
função após ela?Igual a:
Então, sim, isso faz algumas consultas:
$user
$user->team
$user->team->forms
$user->permissible
$user->permissible->groups
$user->permissible->groups->forms
No entanto, o lado profissional é que você não precisa mais usar a política , pois conhece todos os formulários no
$forms
parâmetro são permitidos para o usuário.Portanto, esta solução funcionará para qualquer quantidade de formulários que você tiver no banco de dados.
Se você quiser que seja ainda mais rápido, crie uma consulta personalizada usando a fachada do banco de dados, algo como:
Sua consulta real é muito maior, pois você tem muitas relações.
A principal melhoria de desempenho aqui vem do fato de que o trabalho pesado (a subconsulta) ignora completamente a lógica do modelo Eloquent. Tudo o que resta fazer é passar a lista de IDs para a
whereIn
função para recuperar sua lista deForm
objetos.fonte
Acredito que você possa usar o Lazy Collections para isso (Laravel 6.x) e carregar ansiosamente os relacionamentos antes que eles sejam acessados.
fonte