Torne seu aplicativo escalonável, otimizando o desempenho ORM

A tradução do artigo foi preparada na véspera do início do curso "Desenvolvedor Backend em PHP" .










Olá! Sou Valerio, desenvolvedor italiano e CTO da plataforma Inspector.dev.



Neste artigo, compartilharei um conjunto de estratégias de otimização ORM que utilizo ao desenvolver serviços de back-end.



Tenho certeza de que cada um de nós teve que reclamar que o servidor ou aplicativo está funcionando lentamente (ou mesmo não está funcionando), e passou o tempo na máquina de café esperando os resultados de uma longa solicitação.



Como corrigi-lo?

Vamos descobrir!



O banco de dados é um recurso compartilhado



Por que o banco de dados está causando tantos problemas de desempenho?

Muitas vezes esquecemos que nenhuma consulta é independente das outras.

Achamos que mesmo que uma consulta seja lenta, dificilmente afeta outras ... Mas será que é mesmo?



Um banco de dados é um recurso compartilhado usado por todos os processos executados em seu aplicativo. Mesmo um método mal projetado de acessar o banco de dados pode interromper o desempenho de todo o sistema.



Portanto, não se esqueça das possíveis consequências, pensando: "Tudo bem que esse trecho de código não esteja otimizado!" Um acesso lento ao banco de dados pode levar à sua sobrecarga e isso, por sua vez, pode afetar negativamente a experiência do usuário.



Problema de consulta de banco de dados N + 1



Qual é o problema N + 1?



Este é um problema comum ao usar um ORM para interagir com um banco de dados. Não se trata de escrever código SQL.



Ao usar um sistema ORM como o Eloquent, nem sempre é óbvio quais consultas serão executadas e quando. No contexto desse problema específico, vamos falar sobre relacionamentos e carregamento antecipado.



Qualquer sistema ORM permite declarar relacionamentos entre entidades e fornece uma API excelente para navegar na estrutura do banco de dados.

Abaixo está um bom exemplo para as entidades "Artigo" e "Autor".



/*
 * Each Article belongs to an Author
 */
$article = Article::find("1");
echo $article->author->name; 
/*
 * Each Author has many Articles
 */
foreach (Article::all() as $article)
{
    echo $article->title;
}


No entanto, ao usar relacionamentos em um loop, você deve escrever seu código com cuidado.



Dê uma olhada no exemplo abaixo.



Queremos adicionar o nome do autor ao lado do título do artigo. Com o ORM, você pode obter o nome do autor usando uma relação um a um entre o artigo e o autor.



Tudo parece simples:



// Initial query to grab all articles
$articles = Article::all();
foreach ($articles as $article)
{
    // Get the author to print the name.
    echo $article->title . ' by ' . $article->author->name;
}


Mas então caímos em uma armadilha!



Este loop gera uma solicitação inicial para obter todos os artigos:



SELECT * FROM articles;


e mais N consultas para obter o autor de cada artigo e exibir o valor do campo "nome", mesmo que o autor seja sempre o mesmo.



SELECT * FROM author WHERE id = [articles.author_id]


Recebemos exatamente N + 1 solicitações.



Isso pode não parecer grande coisa. Bem, vamos fazer quinze ou vinte pedidos extras - não é grande coisa. No entanto, vamos voltar à primeira parte deste artigo:



  • — , .
  • , , .
  • , .


:



De acordo com a documentação do Laravel, há uma boa chance de você se deparar com o problema de consulta N + 1, porque quando você acessa relacionamentos do Eloquent como propriedades ( $article->author), os dados de relacionamento são carregados lentamente.



Isso significa que os dados de relacionamento não são carregados até que você acesse a propriedade pela primeira vez.



No entanto, usando um método simples, podemos carregar todos os dados de relacionamento de uma vez. Então, ao acessar a relação Eloquent como uma propriedade, o sistema ORM não executará uma nova consulta porque os dados já foram carregados.



Essa tática é chamada de "carregamento antecipado" e é compatível com todos os ORMs.



// Eager load authors using "with".
$articles = Article::with('author')->get();
foreach ($articles as $article)
{
    // Author will not run a query on each iteration.
    echo $article->author->name;
}


O Eloquent fornece um método with()para carregar relacionamentos avidamente.



Neste caso, apenas duas consultas serão executadas.

O primeiro é necessário para baixar todos os artigos:



SELECT * FROM articles;


O segundo será executado pelo método with()e buscará todos os autores:



SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);


O motor interno do Eloquent mapeia os dados e pode ser acessado da maneira usual:



$article->author->name;


Otimize seus operadores select



Por muito tempo, pensei que declarar explicitamente o número de campos em uma consulta selecionada não levava a uma melhoria significativa de desempenho, então, para simplificar, estava obtendo todos os campos em minhas consultas.



Além disso, especificar rigidamente a lista de campos em uma instrução select específica torna mais difícil manter ainda mais tal parte do código.



A maior armadilha desse argumento é que, do ponto de vista do banco de dados, isso pode realmente ser verdade.



No entanto, estamos trabalhando com um ORM, portanto, os dados selecionados do banco de dados serão carregados na memória no lado do PHP para que o sistema ORM os gerencie posteriormente. Quanto mais campos capturarmos, mais memória o processo ocupará.



O Laravel Eloquent fornece um método de seleção para restringir a consulta apenas às colunas de que precisamos:



$articles = Article::query()
    ->select('id', 'title', 'content') // The fields you need
    ->latest()
    ->get();


Ao excluir campos, o interpretador PHP não precisa processar dados desnecessários, portanto, você pode reduzir significativamente o consumo de memória.



Evitar a busca completa também pode melhorar o desempenho de classificação, agrupamento e mesclagem, pois o próprio banco de dados pode economizar memória como resultado.



Use visualizações no MySQL



As visualizações são consultas SELECT baseadas em outras tabelas e armazenadas no banco de dados.



Quando selecionamos uma ou mais tabelas, o banco de dados primeiro compila nossa instrução SQL, certifica-se de que está livre de erros e, em seguida, busca os dados.



Uma visão é uma instrução SELECT pré-compilada que, quando processada, o MySQL executa imediatamente a consulta interna subjacente da visão.



Além disso, o MySQL é geralmente mais inteligente que o PHP no que diz respeito à filtragem de dados. Há ganhos de desempenho significativos ao usar visualizações sobre o uso de funções PHP para processar coleções ou arrays.



Se você gostaria de aprender mais sobre os recursos do MySQL para desenvolver aplicativos com uso intensivo de banco de dados, verifique este ótimo site: www.mysqltutorial.org



Vincular um modelo do Eloquent a uma visualização



As visualizações também são conhecidas como "tabelas virtuais". Do ponto de vista do ORM, eles se parecem com tabelas regulares.



Portanto, você pode criar um modelo do Eloquent para consultar os dados que estão na visualização.



class ArticleStats extends Model
{
    /**
     * The name of the view is the table name.
     */
    protected $table = "article_stats_view";
    /**
     * If the resultset of the View include the "author_id"
     * we can use it to retrieve the author as normal relation.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}


Os relacionamentos funcionam normalmente, assim como as coerções, a paginação e assim por diante, e não há penalidade de desempenho.



Conclusão



Espero que essas dicas ajudem você a desenvolver um software mais confiável e escalonável.



Todos os exemplos de código são escritos usando o Eloquent como ORM, mas tenha em mente que essas estratégias funcionam da mesma forma para todos os ORMs principais.



Como costumo dizer, precisamos de ferramentas para implementar estratégias eficazes. E se não houver estratégia, não há o que falar.



Muito obrigado por ler o artigo até o fim. Se você quiser saber mais sobre o Inspector, convido você a visitar nosso site www.inspector.dev . Não hesite em escrever para o chat se tiver alguma dúvida!



Postado anteriormente aqui: www.inspector.dev/make-your-application-scalable-optimizing-the-orm-performance





Consulte Mais informação:






All Articles