All for Joomla All for Webmasters
Ruby On Rails

Ruby on Rails (Active Record): Entendendo o uso de joins, includes, preload e eager_load

O grande número de possibilidades para recuperar uma associação das suas tabelas pelo Active Record pode confundir o programador sobre qual método aplicar em cada caso. Além disso, apesar dos métodos de associação serem muito úteis, há um grande risco de perda de performance na sua aplicação se forem utilizados de forma errada. Saber quando usá-los e como combiná-los pode te salvar de grandes dores de cabeça à medida que a sua aplicação cresce. Neste post vamos explorar demonstrações e particularidades de cada método.

joins

Qual é o caso ideal para se utilizar joins?

Se você está apenas filtrando resultados – e não acessa os registros da relação – o joins é a melhor pedida. O exemplo abaixo recupera todos os posts de um blog com um comentário em que o autor é “Ronan”. Lembrando: eu não estou acessando nenhum dos comentários associados, então o joins atende bem:

O joins previne n+1 consultas?

Antes de tudo, deixe-me explicar o que é o problema de n+1 consultas: esse problema ocorre quando você trabalha com associações e itera sobre elementos de uma tabela cujos dados não foram carregados na primeira consulta. Dessa forma, em cada iteração, é necessário fazer uma nova consulta ao banco para recuperar as informações daquele registro (daí o nome n+1, que se refere a essa quantidade de queries). Exemplificando, é bem mais eficiente buscar os 100 comentários de um post em uma única consulta, do que fazer mais 100 consultas para recuperar cada comentário.

Agora, respondendo à pergunta: não. O joins por si só não carrega os dados de uma relação, portanto acessar esses atributos vai gerar as n+1 consultas.

Por exemplo, note a quantidade de consultas adicionais quando nosso acesso utiliza um atributo da relação comentário:

O joins pode ser combinado com includes, preload e eager_load?

Sim. O tipo de junção que é realizado por joins (INNER JOIN por padrão) vai sobrescrever qualquer junção aplicada pelo includes ou eager_load. Note que o preload não aplica uma junção.

includes

O includes previne N+1 consultas?

Sim. O includes vai carregar primeiramente todos os registros do pai e depois todos os registros referenciados como argumentos no método.

Repare que utilizando includes no exemplo anterior apenas executa uma consulta adicional. Sem o includes, haveria uma consulta adicional para contar o número de comentários de cada post:

O includes sempre gera uma consulta separada para recuperar os registros da relação?

Não. O includes vai usar ou uma consulta separada (como acima) ou um LEFT OUTER JOIN. Se você tem uma cláusula where ou order que referencia uma relação, o LEFT OUTER JOIN é aplicado em vez de uma consulta à parte.

O que é mais rápido: uma consulta separada ou ambas na mesma?

Escovando bits no código-fonte do Active Record, não há evidências de que o Active Record decida entre duas ou uma única consulta baseado em performance. Se você está notando piores desempenhos com uma consulta utilizando includes, eu sugiro utilizar uma ferramenta como o Scout DevTrace e examinar qual estratégia está o Active Record está utlizando no includes.

Se duas consultas estão sendo utilizadas, você pode experimentar utilizar uma única com o LEFT OUTER JOIN adicionando a cláusula references:

O que acontece quando eu aplico condições a uma relação referenciada via includes?

O Active Record irá retornar todos os registros pai e apenas os registros que satisfazem a condição para a relação.

Por exemplo, o código a seguir irá retornar todos os posts com um comentário de “Ronan” (e apenas esses comentários são carregados para a memória):

O includes previne todas as consultas n+1?

Não. Se você está acessando algum atributo em uma relação em um nível mais profundo, esses dados não foram carregados. Por exemplo, se você fosse recuperar a relação de likes dos comentários, uma consulta adicional seria executada para cada comentário:

Como eu posso prevenir esse tipo de consulta n+1?

Nesse caso, você pode especificar as associações aninhadas no método includes:

Eu sempre devo carregar os dados das relações aninhadas?

Não. Você pode acabar carregando um grande volume de dados desnecessários. No exemplo anterior, um comentário popular pode ter milhares de likes, que resultaria numa consulta mais lenta com uma alocação de memória significativa.

preload

Eu deveria usar o preload sozinho alguma vez?

Em alguns casos sim, mas não por padrão. Eu uso preload em vez de includes se eu sei que usar um LEFT OUTER JOIN para carregar uma relação é significantemente mais devagar. Caso contrário, se futuramente eu adicionar uma cláusula where ou order, essas clásulas vão desencadear um eager_load, que desencadearia um join.

É comum combinar joins com preload?

Se eu precisar de todos os registros da relação – não apenas os que satisfazem a condição – seria o caso de combinar o preload e joins. Por exemplo:

  1. Encontrar todos os posts com um comentário de “Ronan”
  2. Renderizar esses posts e o total de comentários para cada post

Neste caso, o includes recuperaria apenas os comentários de “Ronan”, mas precisamos também saber o total de comentários de cada post, independente do autor:

eager_load

O includes delega para o eager_load quando há uma cláusula where ou order na relação referenciada.

Eu deveria usar o eager_load sozinho em alguma situação?

Sim. O includes pode apresentar alguma perda de performance utilizando duas consultas e utilizar o eager_load vai forçar uma consulta única com um LEFT OUTER JOIN. Utilizá-lo no seu código garante que futuramente você não terá problemas de performance por executar duas consultas.

Eu posso combinar eager_load com joins?

Sim. No exemplo a seguir:

O Active Record vai fazer o seguinte:

  1. Retornar um array de posts com os comentários.
  2. Carregar os comentários associados com cada post.

É um includes com um INNER JOIN em vez de um LEFT OUTER JOIN.

Se liga aí, porque é a hora da revisão

O resumo por alto da utilização dos métodos seria mais ou menos assim:

  1. Se você está apenas filtrando, utilize joins.
  2. Se você está acessando as relações, comece com o includes.
  3. Se o includes está lento e executando duas consultas, force o eager_load e faça uma comparação.

Existem muitos casos à parte quando você está acessando relações utilizando o Active Record e sua estratégia pode variar. Mas essas recomendações cobrem a grande maioria dos casos e é suficiente para prevenir os problemas de performance mais comuns. Quaisquer dúvidas ou sugestões, utilize a área de comentários ou entre em contato!


Referência: http://blog.scoutapp.com/articles/2017/01/24/activerecord-includes-vs-joins-vs-preload-vs-eager_load-when-and-where

Você Também Pode Gostar

2 Comentários

  • Responder
    Sannytet
    12 de dezembro de 2018 at 00:04

    Nice posts! 🙂
    ___
    Sanny

  • Responder
    kleber
    1 de setembro de 2020 at 17:14

    Obrigado. Esclarecedor!

  • Deixe uma resposta para Sannytet Cancelar Resposta