O que é o problema de queries N+1 e como resolvê-lo no Rails?

Que o Active Record é uma “mão-na-roda” nós já sabemos, mas saber usá-lo corretamente é de suma importância para permitir que aplicações Rails cresçam sem problemas de performance.

Nesse artigo, vamos falar desse que é um dos pequenos problemas que podem afetar a sua aplicação quando se utiliza do Active Record de forma errada.

O problema de queries N+1 não é novidade, mas, é comum muitos desenvolvedores Rails esquecerem de dar atenção a isso achando que o Rails é a bala de prata para todos os problemas e que magicamente tudo se resolve.

Para a tristeza de alguns, é necessário colocar a mão na massa e fazer alguns pequenos ajustes, ou mesmo, adotar algumas práticas para que o projeto não desande, principalmente por problemas de performance.

Pois bem, para entender o referido problema criei um pequeno exemplo com 2 CRUDs relacionados da seguinte forma:

Então, vamos começar fazendo o clone e o setup inicial de uma app que deixei pronta para esse propósito. Rode os comandos a seguir:

git clone https://github.com/jacksonpires/crud_exemplo.git
cd crud_exemplo
rails db:create db:migrate db:seed
rails s

Prontinho. Agora que a aplicação já está rodando, acesse http://localhost:3000/clients e você verá algo parecido com isso:

Ou seja, devem ser mostrados os nomes e as respectivas profissões de 5 clientes pois em app/controllers/clients_controller.rb, na action index temos:

Até aí tudo normal, mas, vamos analisar o log gerado por esse acesso.

Perceba que foram feitas 6 queries SQL no banco de dados, sendo que a primeira foi para consultar os 5 clientes e as outras 5 para consultar a profissão de cada um dos clientes. Ou seja, tivemos 6 queries no total.

Agora imagine se tivéssemos 1000 clientes para serem listados com suas profissões, isso geraria 1001 queries SQL (daí surge o nome N+1), quando na verdade isso pode ser resolvido com apenas 2 queries, em qualquer situação desse aspecto.

A grande sacada é que o Active Record, por padrão, não vai fazer as queries SQL levando em conta as associações, ele só vai buscar o nome da profissão (que está em outra tabela) no momento em que a gente precisar mostrá-lo. A isso damos o nome de “lazy loading”, pois “preguiçosamente” ele busca apenas os clientes, deixando pra depois o nome das profissões.

Para nossa sorte o próprio Active Record permite usarmos a técnica de “eager loading” (carregamento ‘ansioso’), que nada mais é que carregar previamente tudo que precisamos como resposta do banco de dados.

Para atingir esse objetivo, vamos alterar apenas o app/controllers/clients_controller.rb, na action index, adicionando o .includes(:occupation), conforme abaixo.

Isso fará que as profissões sejam todas carregadas logo após a pesquisa dos clientes.

Acessando mais uma vez o http://localhost:3000/clients e verificando o log agora temos apenas 2 queries, onde a primeira pesquisou os 5 clientes e a segunda pesquisou as 5 profissões dos clientes já encontrados. Veja:

Simples, não? 🙂

Então é isso, gente! Otimizem suas pesquisas para não sofrer com isso quando o seu software estiver em produção.

Como sempre, não esqueçam de curtir nossa página no facebook, nos seguir nas redes sociais e também de se cadastrar em nossa newsletter semanal.

Um super abraço e até a próxima! 😉