4 Dicas para Resolver Problemas de N+1 Queries em Ruby on Rails


Ruby on Rails

Os problemas de N+1 queries em Ruby on Rails ocorrem quando sua aplicação faz um número excessivo de consultas ao banco de dados devido ao carregamento das associações entre modelos. Isso pode causar uma degradação significativa no desempenho, especialmente conforme o volume de dados aumenta.

Neste post, vamos abordar quatro maneiras eficazes de resolver o problema de N+1 queries em Rails, tornando sua aplicação mais rápida e eficiente.

1. Use includes para Eager Loading

A solução mais comum para problemas de N+1 é o uso de includes. Esse método permite carregar registros associados em uma única consulta, em vez de consultar o banco de dados cada vez que uma associação é acessada.

Sem includes (Problema de N+1):

posts = Post.all
posts.each do |post|
  post.comments.each do |comment|
    puts comment.body
  end
end

Neste exemplo, para cada post, o Rails executará uma consulta separada para buscar os comentários associados. Isso pode resultar rapidamente em dezenas ou até centenas de consultas à medida que o número de posts cresce.

Com includes (Resolvendo N+1):

posts = Post.includes(:comments).all
posts.each do |post|
  post.comments.each do |comment|
    puts comment.body
  end
end

Ao usar includes(:comments), o Rails carregará os posts e seus comentários associados em uma única consulta, evitando o problema de N+1.

2. Use joins para Eficiência nas Consultas

Se você não precisa carregar os registros associados na memória e só precisa deles para filtros ou condições, o uso de joins é uma solução mais eficiente. Enquanto includes carrega os registros associados, joins utiliza SQL para combinar os dados sem buscar todos os registros relacionados.

Exemplo:

Post.joins(:comments).where(comments: { approved: true })

Neste caso, joins cria um INNER JOIN em SQL entre as tabelas de posts e comentários, permitindo que você filtre os posts com comentários aprovados sem carregar todos os dados dos comentários na memória.

3. Considere preload ou eager_load em Casos Específicos

Tanto preload quanto eager_load são variações de eager loading no Rails, mas funcionam de maneira um pouco diferente de includes.

  • preload: Carrega os registros associados em consultas separadas, em vez de usar um join. Isso pode ser útil quando você não quer lidar com joins, mas ainda precisa evitar o problema de N+1.
  • eager_load: Força o Rails a usar um LEFT OUTER JOIN para carregar tanto os registros primários quanto os associados em uma única consulta.

Quando usá-los:

  • Use preload se você estiver lidando com um grande conjunto de dados e quiser evitar joins complexos.
  • Use eager_load quando quiser garantir que todas as associações sejam carregadas em uma única consulta.

Exemplo:

Post.preload(:comments).all # Consultas separadas para posts e comentários
Post.eager_load(:comments).all # Uma única consulta com LEFT OUTER JOIN

4. Selecione Apenas os Dados Necessários

Outra maneira de otimizar suas consultas é selecionar apenas as colunas que você precisa do banco de dados. Por padrão, o Rails carrega todas as colunas das tabelas associadas, mas muitas vezes você não precisa de todas elas.

Você pode usar select para carregar apenas os dados relevantes:

posts = Post.select(:id, :title).includes(:comments).all
posts.each do |post|
  puts post.title
end

Neste caso, em vez de carregar todas as colunas da tabela de posts, estamos selecionando apenas as colunas id e title, reduzindo a quantidade de dados sendo buscados no banco de dados.


Com essas quatro dicas, você pode melhorar significativamente o desempenho da sua aplicação Rails, evitando o problema de N+1 queries e tornando suas consultas ao banco de dados mais eficientes.