Como desenvolvedor Ruby on Rails, é importante entender como otimizar consultas a bancos de dados para melhorar o desempenho e aprimorar a experiência do usuário. O Active Record, a ferramenta ORM (Object-Relational Mapping) do Rails, oferece recursos poderosos para consultar bancos de dados de forma eficiente.

A otimização de consultas é um assunto complexo, com muitos livros escritos sobre isso. Aqui, exploraremos algumas técnicas e dicas para otimizar suas consultas do Active Record e aumentar a velocidade e a capacidade de resposta do seu aplicativo.

Use a recuperação seletiva de colunas

Uma das maneiras mais eficazes de otimizar as consultas do Active Record é recuperar apenas as colunas necessárias do banco de dados. Ao especificar as colunas exatas de que precisa, você minimiza os dados transferidos entre o banco de dados e o aplicativo Ruby on Rails. Por exemplo, se você quiser usar apenas os nomes no banco de dados:

# Unoptimized Practice: Retrieving all columns
User.all

# Optimized Practice: Selecting specific columns
User.select(:id, :name)

Use o eager loading

O eager loading (carregamento ansioso) ajuda a reduzir o número de consultas ao banco de dados, adiantando o carregamento de registros associados. Com o pré-carregamento das associações, você evita o problema de consulta N+1, em que consultas adicionais são executadas para cada registro associado. Veja um exemplo do problema de consulta N+1 abaixo, e então apresentaremos uma técnica alternativa chamada Russian Doll Caching.

# N+1 query problem
users = User.all
users.each { |user| puts user.posts.count }  # Executes one query for users and N queries for posts (N = number of users)

No exemplo acima, buscamos todos os usuários e, em seguida, iteramos sobre cada usuário para recuperar a contagem de suas publicações associadas. Isso resulta em N consultas adicionais sendo executadas, levando à degradação do desempenho.

Para superar esse problema, podemos usar o eager loading com o método includes, conforme mostrado abaixo:

# Eager loading solution
users = User.includes(:posts).all
users.each { |user| puts user.posts.count }  # Executes two queries: one for users and one for posts (regardless of user count)

Usando includes(:posts), carregamos as postagens associadas de todos os usuários em apenas duas consultas. O método includes pré-carrega com eficiência os dados de associação, eliminando a necessidade de consultas adicionais e melhorando significativamente o desempenho.

Técnica alternativa: Russian Doll Caching

Além do eager loading, uma técnica alternativa para otimizar consultas ao banco de dados é o Russian Doll Caching. Essa técnica envolve o armazenamento em cache de estruturas de dados hierárquicas e suas associações, permitindo recuperação eficiente sem consultas redundantes.

Vamos considerar um exemplo em que recuperamos uma lista de publicações de blog e seus comentários associados:

# Without caching (N+1 query problem)
@posts = Post.all
@posts.each do |post|
  @comments = post.comments
  # Perform actions with comments
end

No código acima, cada iteração do loop aciona uma consulta para buscar os comentários de cada publicação, o que resulta em N consultas adicionais.

Para implementar o Russian Doll Caching, podemos usar uma abordagem como o cache de fragmentos. Ao armazenar em cache a exibição inteira ou parcial, incluindo os registros associados, podemos evitar consultas redundantes. Aqui está um exemplo:

# With Russian Doll Caching
<% cache @posts do %>
  <% @posts.each do |post| %>
    <% cache post do %>
      <%= post.title %>
      <% post.comments.each do |comment| %>
        <%= comment.content %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Nessa implementação, armazenamos em cache o objeto @posts e cada publicação individual usando o auxiliar cache. Ao renderizar a exibição ou parte dela, o Rails verifica o cache antes de executar qualquer código, eliminando a necessidade de consultas adicionais.

Ao implementar o Russian Doll Caching, você pode otimizar o desempenho minimizando as consultas ao banco de dados e recuperando com eficiência as estruturas de dados hierárquicas e suas associações.

O eager loading é uma técnica eficiente para evitar o problema de consulta N+1 por meio do pré-carregamento de associações. Além disso, o Russian Doll Caching oferece uma abordagem alternativa para otimizar as consultas ao banco de dados, armazenando em cache as estruturas de dados hierárquicas e suas associações.

Ao empregar essas técnicas, você pode aumentar o desempenho e a capacidade de resposta dos seus aplicativos Ruby on Rails. Escolha a abordagem que melhor se adapta às necessidades e complexidades do seu aplicativo.

Existem gems que o ajudarão a identificar consultas N+1 enquanto você estiver desenvolvendo o aplicativo. Gems como Bullet, Rack Mini Profiler e Prosopite são alguns exemplos que vale a pena experimentar em seu projeto.

Utilize a indexação

Os índices melhoram o desempenho das consultas, permitindo que o banco de dados localize os registros mais rapidamente. No Active Record, você pode adicionar índices ao schema do banco de dados, principalmente em colunas usadas com frequência nas consultas. Por exemplo:

# Add index to improve performance
add_index :users, :email

Além disso, há gems que podem ajudá-lo a identificar onde você deve adicionar índices, como as gems lol_dba ou database_consistency.

Otimize consultas ao banco de dados com condições

Ao criar consultas, considere o uso de recursos específicos do banco de dados para condições, a fim de evitar a recuperação desnecessária de dados. O Active Record oferece vários métodos para otimizar as condições de consulta, como where, limit, offset e order. Veja um exemplo:

# Unoptimized query
users = User.all
users.select { |user| user.age > 18 && user.age < 25 }

# Optimized query
users = User.where(age: 19..24).all

Processamento em lote para grandes conjuntos de dados

Trabalhar com grandes conjuntos de dados pode afetar o desempenho devido a restrições de memória. Considere o uso de técnicas de processamento em lote para dividir as consultas em partes menores, reduzindo o uso da memória. Essa abordagem é especialmente útil ao realizar operações como atualização ou exclusão de registros.

No entanto, é importante que você use o processamento em lote corretamente para obter o desempenho ideal. Vejamos um exemplo de processamento em lote inadequado e como ele pode afetar negativamente o seu aplicativo:

# Unoptimized Practice: Naive batch processing
users = User.all
users.each do |user|
  # Perform operations on user record
end

No snippet de código acima, buscamos todos os registros de usuários do banco de dados usando User.all. Isso pode representar um problema significativo de desempenho ao lidar com grandes conjuntos de dados, pois carrega todos os registros na memória de uma só vez. Como resultado, o aplicativo pode consumir recursos excessivos de memória e ficar mais lento.

Para resolver esse problema, vamos refatorar o código usando uma abordagem de processamento em lote mais otimizada:

# Optimized Practice: Batch processing with `find_in_batches`
User.find_in_batches(batch_size: 1000) do |users_batch|
  users_batch.each do |user|
    # Perform operations on user record
  end
end

Nessa implementação atualizada, usamos o método find_in_batches fornecido pelo Active Record. Esse método busca registros em lotes menores, especificados por batch_size, reduzindo o espaço ocupado na memória. Ele processa cada lote de registros em seu próprio contexto de memória, melhorando consideravelmente o desempenho do aplicativo ao lidar com grandes conjuntos de dados.

Com o uso de find_in_batches, você pode processar grandes conjuntos de dados de forma eficiente em termos de memória. Lembre-se de ajustar o batch_size com base na necessidade do seu aplicativo e nos recursos disponíveis do sistema.

Resumo

Otimizar as consultas do Active Record é fundamental para melhorar o desempenho dos seus aplicativos Ruby on Rails. Seguindo as dicas descritas neste artigo — incluindo recuperação seletiva de colunas, eager loading, indexação, otimização de condições e processamento em lote — você pode aumentar significativamente a velocidade e a eficiência das suas consultas ao banco de dados.

Lembre-se de que o ajuste fino das consultas não apenas melhora a experiência do usuário, mas também reduz a carga no servidor de banco de dados. Mantendo essas técnicas de otimização em mente, seu aplicativo Ruby on Rails será executado sem problemas mesmo com grandes quantidades de dados. Boa codificação!

Lee Sheppard

Lee is an Agile certified full stack Ruby on Rails developer. With over six years in the tech industry he enjoys teaching, coaching Agile, and mentoring others. Lee also speaks at tech related events and has a background in design and illustration.