DEV Community

Roberson Miguel
Roberson Miguel

Posted on

Otimização de consultas com ActiveRecord e índices no Rails

Toda esses meus posts, são resultados de anos de anotações e documentação de trabalhos realizados, pode ser que algum detalhe esteja inconsistente, pois alterei as bases originais para pode postar publicamente.

Então vamos lá...

Para garantir que nossa aplicação Rails seja eficiente, especialmente em termos de acesso a dados, é crucial otimizar as consultas ao banco de dados. Uma das maneiras mais eficazes de fazer isso é utilizando índices nos campos de nossas tabelas que são frequentemente usados em consultas. Vamos explorar como podemos usar o ActiveRecord em conjunto com o console IRB para criar e testar índices que melhorem a performance de nossas queries.

O que são Índices?

Índices são estruturas de dados que melhoram a velocidade das operações de busca em tabelas de banco de dados. Eles são parecidos com os índices dos livros, eles permitem localizar rapidamente uma informações. Em um banco de dados, os índices podem ser criados em colunas ou combinações de colunas para acelerar consultas que filtram ou ordenam por essas colunas.

Passos a passo

1. Identificação de consultas lentas

Primeiro, devemos identificar quais consultas estão levando mais tempo para serem executadas. Isso pode ser feito através de logs de execução de queries ou ferramentas de monitoramento de performance de banco de dados.

No Rails, você pode habilitar o log detalhado de consultas no ambiente de desenvolvimento:

config.active_record.verbose_query_logs = true
Enter fullscreen mode Exit fullscreen mode

2. Utilização do console IRB para testes

O console IRB é uma ferramenta poderosa para interagir diretamente com sua aplicação Rails e seu banco de dados. Para iniciar o console IRB, basta rodar:

rails console
Enter fullscreen mode Exit fullscreen mode

Com o console IRB aberto, você pode testar suas consultas e verificar seu desempenho. Por exemplo:

User.where(email: 'example@example.com').explain
Enter fullscreen mode Exit fullscreen mode

O método explain retorna o plano de execução da consulta, mostrando como o banco de dados planeja executá-la e onde pode haver gargalos.

Saída fictícia do plano de execução antes do Índice:

EXPLAIN for: SELECT "users".* FROM "users" WHERE "users"."email" = 'example@example.com'
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows     | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+
| 1  | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL | 1000000  | Using where |
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+

Enter fullscreen mode Exit fullscreen mode

Explicação:

type: ALL: Indica que está fazendo uma varredura completa da tabela (full table scan), o que é ineficiente para tabelas grandes.

rows: 1000000: O número de linhas que o banco de dados precisa examinar.

3. Criação de Índices

Após identificar as consultas que podem ser otimizadas, o próximo passo é criar índices nas colunas apropriadas.

Exemplo de migração para criar Índices:

rails generate migration add_index_to_users_email
Enter fullscreen mode Exit fullscreen mode

Edite o arquivo de migração gerado para adicionar o índice:

class AddIndexToUsersEmail < ActiveRecord::Migration[6.0]
  def change
    add_index :users, :email, unique: true
  end
end
Enter fullscreen mode Exit fullscreen mode

Execute a migração:

rails db:migrate
Enter fullscreen mode Exit fullscreen mode

4. Verificação da Performance Após a Criação do Índice

Depois de adicionar o índice, volte ao console IRB e execute novamente a consulta para verificar a melhoria na performance:

User.where(email: 'example@example.com').explain
Enter fullscreen mode Exit fullscreen mode

Você deverá notar que o plano de execução da consulta agora utiliza o índice, resultando em uma busca mais rápida.

Saída fictícia do plano de execução depois do Índice:

EXPLAIN for: SELECT "users".* FROM "users" WHERE "users"."email" = 'example@example.com'
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows     | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+
| 1  | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL | 1000000  | Using where |
+----+-------------+-------+------+---------------+------+---------+------+----------+-------------+
Enter fullscreen mode Exit fullscreen mode

Explicação:

type: ALL: Indica que está fazendo uma varredura completa da tabela (full table scan), o que é ineficiente para tabelas grandes.

rows: 1000000: O número de linhas que o banco de dados precisa examinar.

Boas práticas para uso de Índices

  1. Índices em colunas com filtro frequente: Crie índices em colunas que são frequentemente usadas em cláusulas WHERE.

  2. Índices em colunas de ordenação: Colunas que são frequentemente utilizadas em cláusulas ORDER BY também se beneficiam de índices.

  3. Evitar Índices desnecessários: Índices ocupam espaço em disco e podem impactar negativamente a performance de operações de escrita, como INSERT e UPDATE. Use-os de forma judiciosa.

  4. Índices combinados: Em alguns casos, índices em múltiplas colunas podem ser benéficos, especialmente se essas colunas são frequentemente usadas juntas em consultas.

Exemplo prático: Índice combinado

Suponha que temos uma tabela orders com as colunas user_id e created_at, e frequentemente queremos buscar todos os pedidos de um usuário ordenados pela data de criação.

Criação de um Índice combinado:

rails generate migration add_index_to_orders_user_id_and_created_at
Enter fullscreen mode Exit fullscreen mode

Edite o arquivo de migração gerado:

class AddIndexToOrdersUserIdAndCreatedAt < ActiveRecord::Migration[6.0]
  def change
    add_index :orders, [:user_id, :created_at]
  end
end
Enter fullscreen mode Exit fullscreen mode

Execute a migração:

rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Verificação da performance:

No console IRB, execute a consulta otimizada:

Order.where(user_id: 1).order(created_at: :desc).explain
Enter fullscreen mode Exit fullscreen mode

Com o índice combinado, o banco de dados poderá localizar os registros de forma mais eficiente, resultando em consultas mais rápidas.

Saída fictícia do plano de execução depois do Índice:

EXPLAIN for: SELECT "users".* FROM "users" WHERE "users"."email" = 'example@example.com'
+----+-------------+-------+-------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys | key    | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+---------------+--------+---------+-------+------+-------------+
| 1  | SIMPLE      | users | ref   | index_users_on_email | index_users_on_email | 767  | const | 1    | Using index |
+----+-------------+-------+-------+---------------+--------+---------+-------+------+-------------+
Enter fullscreen mode Exit fullscreen mode

Explicação:

type: ref: Indica que está utilizando o índice para encontrar os registros, o que é muito mais eficiente.

rows: 1: O número de linhas que o banco de dados precisa examinar é drasticamente reduzido, mostrando que o índice está sendo utilizado.

Conclusão

O uso adequado de índices é essencial para otimizar a performance de consultas em aplicações Rails. Utilizando o ActiveRecord e o console IRB, podemos identificar consultas lentas, criar índices apropriados e verificar os ganhos de performance. Ao seguir essas práticas, você garante que sua aplicação seja rápida e escalável, proporcionando uma melhor experiência para os usuários finais.

Top comments (0)