
Quando se está lidando com um alto volume de dados, com milhares, e até milhões de rows, algumas metodologias comuns que utilizamos no Rails começam a se tornar obsoletas e/ou menos efetivas.
O Active Record é uma classe ORM (Object Relational Mapping) que mapeia objetos em tabelas do banco de dados. Ele monta as queries e realiza essa comunicação com o banco de dados, facilitando a vida dos desenvolvedores de plantão hahahaha 😀
Usar o Active Records é extremamente vantajoso, tanto em questão de performance quanto em simplicidade para bancos de pequeno/médio porte, mas quando se trata de bancos de grande porte, algumas outras abordagens acabam trazendo mais performance às respostas e é sobre isso que falaremos aqui no OneBitCode \o/
Nesse artigo, vamos aprender a otimizar nossas queries lidando com um alto volume de rows no BD!
Então, sem mais enrolação, bora lá!
Dependências:
- Ruby
- Rails
Passo a passo
Criando o Projeto de exemplo
1 – Gere um projeto com o seguinte comando:
1 |
rails new fastest-queries |
2 – Entre no projeto criado:
1 |
cd fastest-queries |
3 – Gere o model/controller/view com o seguinte código:
1 |
rails g scaffold person name:string age:integer document:string country:integer favorite_color:integer |
Inserindo e visualizando os dados
1 – Substitua seu model/person.rb por:
1 2 3 4 |
class Person < ApplicationRecord enum country: [:brazil, :us, :india] enum favorite_color: [:red, :blue, :yellow] end |
2 – Substitua seu db/seeds.rb pelo seguinte código:
1 2 3 4 |
# Generate 10000 records 10000.times do |i| Person.create(name: "Person #{i}", age: rand(100), document: "999.999.999-99", country: rand(2), favorite_color: rand(2)) end |
3 – Rode o seeds com o seguinte código:
1 |
rake db:create db:migrate db:seed |
4 – Entre no terminal com o seguinte comando:
1 |
rails c |
5 – Executando o comando Person.last deverá trazer um objeto como na imagem a seguir:
6 – Agora modifique seu config/routes.rb e deixe-o da seguinte forma:
1 2 3 4 |
Rails.application.routes.draw do root "people#index" resources :people end |
7 – Execute o projeto com o comando rails s. Abrindo o endereço localhost:3000 no navegador, deve ser exibida uma página como essa:
Limitando os dados exibidos
1 – Substitua o código da view people/index.html.erb pelo seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<p id="notice"><%= notice %></p> <h1>People</h1> <hr /> <h4>Busque por uma faixa de idade!</h4> <%= form_for(root_path, method: "get") do |f| %> De: <%= number_field_tag :from %> Até: <%= number_field_tag :to %> <%= f.submit 'Procurar' %> <% end %> <hr /> <table> <thead> <tr> <th>Name</th> <th>Age</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @people.each do |person| %> <tr> <td><%= person.name %></td> <td><%= person.age %></td> <td><%= link_to 'Show', person %></td> <td><%= link_to 'Edit', edit_person_path(person) %></td> <td><%= link_to 'Destroy', person, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Person', new_person_path %> |
2 – Modificamos a view para retirar do banco somente o que vamos usar na view, otimizando o mesmo.
Situação típica de uma empresa com muitos atributos associados ao model, mas você se encontra em uma situação que só precisa de alguns desses atributos.
Ao recarregar a página do navegador, ela deverá aparecer da seguinte forma:
Medindo a velocidade da query e otimizando
1 – Em seu controller controllers/people_controller.rb substitua o método index pelo seguinte código:
1 2 3 4 5 6 7 |
def index if params[:to].present? && params[:from].present? @people = Person.where("age >= ? AND age <= ?", params[:from].to_i, params[:to].to_i).order(age: :desc) else @people = Person.all end end |
2 – Na busca acima, estamos usando a query no modelo do Active Record. Atualizando a página na view, e clicando em “Procurar”, devemos filtrar as idades com a seguinte performance:
3 – No exemplo acima, filtramos as idades de 10 a 100. Podemos observar que a view levou 2115.1ms para ser carregada, e o ActiveRecord, 74.5ms.
4 – Agora vamos dar um pouco mais de performance à nossa query!
5 – Substitua o código do método index do seu people_controller pelo código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def index if params[:to].present? && params[:from].present? @people = Person.find_by_sql([ "SELECT id, name, age FROM people WHERE (age >= ?) AND (age <= ?) ORDER BY age DESC", params[:from], params[:to] ]) else @people = Person.all end end |
Usamos o método find_by_sql para executar o SQL diretamente (sem o active records fazendo isso para nós).
6 – Fazendo uma nova busca com os mesmos parâmetros buscados pela primeira vez, nós conseguimos o seguinte resultado:
7 – Só nesse pequeno projeto conseguimos reduzir de 74.5ms de carregamento do ActiveRecord, para 25.4ms de carregamento. Um carregamento 3 vezes mais rápido \o/.
Conclusão
Obtivemos esse resultado em um mini projeto, usando poucos atributos e um número de rows limitados. Imagine em empresas realmente grandes, com centenas de milhares de records no banco de dados… Uma otimização dessas pouparia preciosos segundos, e até minutos de diferença no carregamento!!
Uma observação é que ao lidar com BDs com uma grande quantidade de dados, o mais indicado é o uso de queries SQL ao máximo, evitando a automatização de buscas e o aparecimento de lazy queries (Percorrer uma query a mais, desnecessária na busca, aumentando o tempo de carregamento por uma má formação da query SQL no banco de dados) causado pelo Active Record.
Obrigado por estar com a gente o/
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade, e em português, sobre programação com foco em Ruby on Rails e também JavaScript.
Além disso, aqui sempre levamos à você conteúdos valiosos sobre a carreira de programação, dicas sobre currículos, portfólios, perfil profissional, soft skills, enfim, tudo o que você precisa saber para continuar evoluindo como Programador(a)!
Fique por dentro de todos os conteúdos o/
Nossas redes sociais:
📹 • https://youtube.com/Onebitcode [Live todas as terças-feiras às 19h)
💻 • https://linkedin.com/company/onebitcode
🙂 • dating advice over 40
📱 • columbus gay escort
🐦 • https://twitter.com/onebitcode
Nossos cursos:
🥇 • Programador Full Stack Javascript em 8 Semanas
💎 • Curso Completo de Ruby
⚙ • Minicurso: API Rails 5 Completo
🐞 • Minicurso de Testes para Ruby on Rails com RSpec
Espero que curta nossos conteúdos e sempre que precisar de ajuda, fala com a gente!
Estamos aqui para você 🙂
Bem-vindo à família OneBitCode o/
jbuilder tb é bomzão
Usar SQL puro não seria o mais prático, por qual motivo você não utilizou o .select(:name, :age) que já iria modificar o select pra trazer somente os campos utilizados, com os filtros .where?
Tambem pensei o mesmo, testei aqui e deu uma diferença minima entre usar o find_by_sql e o que rails oferece ( select, group, where, order, includes… )
Lucas seu conteúdo é ótimo parabéns! Entendo o que você quis demonstrar, mas não vejo tanto ganho real nesse caso de uso, conforme já disseram. O que mais vejo causar lentidão em sistemas com grande massa de dados são as query n+1, para resolver esses problemas no seu projeto eu recomendo usar a gem bullet https://github.com/flyerhzm/bullet, você pode configura-la para avisar nos seus testes quando ocorrer um caso de query n+1. Enfim, existem muitas formas de otimizar suas consultas, o que realmente vai valer a pena no final das contas é vc testar suas buscas com diferentes formas, sempre buscando… Read more »