O problema N+1 é um anti-pattern, conhecido por ser uma maneira ineficiente de realizar consultas numa base com um número relativamente grande de dados.
Neste artigo iremos explicar o comportamento desse problema, como identificá-lo e como corrigi-lo, mantendo o desempenho das suas aplicações eficiente.
Como ele se comporta?
Vamos imaginar que temos numa base de dados duas tabelas, uma de usuários e outra de artigos.
Base dedados com duas tabelas.
A tabela de usuários armazena as informações de cada usuário registrado no sistema e a tabela de artigos armazena as informações dos artigos de cada usuário. Logo temos uma relação de 1 para N, sendo o N o número de artigos que cada usuário escreveu.
Imagine que você precisa desenvolver um dashboard onde será exibido dados de todos os usuários e os títulos de todos os artigos de cada um deles. O que provavelmente você pensaria em fazer de primeira seria:
1°- Buscar pelas informações de todos os usuários:
SELECT ID, name, email FROM Users;
2°- Em seguida consultar o título de todos os artigos de cada usuário:
SELECT title FROM Articles WHERE user_id = id_usuario;
Agora pense que temos 500 usuários registrados na nossa base
As consultas serão executadas da seguinte maneira:
SELECT ID, name, email FROM Users;
SELECT title FROM Articles WHERE user_id = 1;
SELECT title FROM Articles WHERE user_id = 2;
SELECT title FROM Articles WHERE user_id = 3;
SELECT title FROM Articles WHERE user_id = 4;
…
SELECT titleFROM Articles WHERE user_id = 500;
O que podemos reparar é que no final executamos 501 consultas ao nosso banco de dados, por isso o nome N+1, sendo N o número de usuários.
Nesse exemplo acima temos poucas colunas nas nossas tabelas, mas muitas vezes, em sistemas empresariais de médio à grande porte, o número de colunas numa tabela pode ser bem maior. Isso faz com que o tempo que nossas consultas irão levar e o tempo de resposta da nossa aplicação aumente. O que afinal de contas, não é nada bom.
Como identificar o problema N+1?
Na grande maioria das vezes, quando temos uma consulta dentro de um loop temos o problema N+1. Trazendo um pouco mais para o dia a dia do desenvolvedor, abaixo deixo um pseudocódigo exemplificando este cenário:
Em seguida, abra o ambiente do Airflow clicando na opção Airflow
Como podemos notar, faremos uma primeira consulta que nos trás todos os usuários. Logo após,faremos N consultas, sendo N o número de usuários e para cada iremos trazer os artigos relacionados a ele.
Como resolver o problema N+1?
Agora vem aparte boa, vamos transformar este número de 501 consultas para apenas 2!
Primeiramente,iremos continuar fazendo a consulta que nos trás os usuários:
SELECT ID, name, email FROM Users;
Agora,iremos utilizar estas informações que temos para fazer a nossa segunda consulta. Nessa precisaremos antes de tudo iterar sobre nosso array de usuários para termos apenas um novo array com todos os IDs. Ficando da seguinte maneira :
SELECT title FROM Articles WHERE user_id IN (array_of_users_id);
// SELECT title FROM Articles WHERE user_id IN (1,2,3,4,…,500);
Alterando o pseudocódigo que montamos anteriormente, ele seguiria a seguinte lógica:
Assim , oque fizemos de modificação foi apenas aproveitar os dados retornados dos usuários para criar um novo array, mas agora apenas com o ID de cada usuário, e realizar uma consulta no banco por todos os artigos que possuem relação com um dos IDs informados no operador IN. Totalizando apenas 2 consultas feitas ao banco.
Mas, e se usarmos JOIN?
Se você possui experiência com SQL, perceberá que podemos ao invés de usar o operador IN,realizar um JOIN e com isso apenas precisaríamos de 1 consulta.
O número de consultas pode afetar no desempenho da nossa aplicação, mas isso não significa que por ser 1 consulta será mais eficiente.
SELECT Users.ID, Users.nome, Articles.title FROM Users INNERJOIN Articles ON Articles.user_id = Users.ID;
Por exemplo,se cada usuário apenas tivesse um artigo escrito, o JOIN seria mais eficiente.Mas se cada usuário tiver em média 5 artigos, teremos muitos dados repetidos trafegando entre o banco de dados e a aplicação, o que pode consumir tempo e memória.
Conclusão
No final das contas, a modificação que fizemos para alterar o número de consultas na nossa base foi bem simples, mas muito poderosa.
Com ela tivemos um ganho de desempenho muito alto, deixando o tempo de resposta da nossa aplicação menor e consequentemente fazendo nosso cliente final muito mais feliz.
O objetivo desse artigo era te mostrar como boas análises feitas com calma, podem refletir de maneira significativa no comportamento do seu sistema.
Esperamos que tenha ajudado você a refletir sobre a maneira com que anda consumindo os dados da sua base e na maneira com que projeta suas aplicações.
Gostou do artigo? Confira agora o nosso artigo de introdução ao Interceptors NestJS!