Criar códigos para relatórios e processamentos complexos pode ser um processo exaustivo, com muitas repetições e códigos longos demais.
É para resolver essas questões que usamos o memoization, que nada mais é do que uma técnica de otimização do código, que pode acelerar a performance ao armazenar resultados em cache, e retornando-os quando requeridos.
Quer entender como aplicar o memoization aos seus projetos? Continue lendo este artigo e aprenda quando e como utilizar essa técnica.
Memoization é uma das formas de otimização do código para que o cache seja o primeiro resultado de uma função, e retorne posteriormente.
Essa técnica evita que um novo processamento seja feito repetidas vezes, aumentando a velocidade da ação.
O memoization pode ser utilizado para diferentes objetivos, sendo os principais:
Porém, é preciso sempre analisar se o memoization é a melhor opção para otimizar o cache.
Por ser uma técnica aplicada à linguagem de código, o memoization pode ser aplicado a diferentes tecnologias.
Para ilustrar alguns exemplos do uso do memoization, na prática, vamos utilizar a linguagem Ruby.
Vamos pensar em um cenário onde um usuário consiga ver a quantidade de posts criado por ele logo na página inicial, em um dashboard.
Assim que ele acessar a página, esta query já será executada e em alguma parte da página será exibido este resultado.
Imagine isto sendo exibido (por algum motivo) em dois, três ou mais lugares (na mesma requisição) e sem a otimização.
O problema também fica mais complicado se você usa um banco de dados pago por leitura (como o Firebase ou DynamoDB).
Além de economizar os custos do projeto, o memoization garante que a velocidade de resposta também seja otimizada.
Analise o exemplo abaixo de uma query sem otimização:
class User |
E como conseguimos otimizar esta chamada? Veja abaixo:
class User |
Com essa comparação, pode surgir a dúvida sobre o uso do cache ao invés do memoization.
Porém, esse método é mais assertivo, garantindo que não ocorra a invalidação de cache.
Além das queries, o memoization também pode ser utilizado para a geração de relatórios.
Utilizando o exemplo abaixo, com um relatório que utiliza a query anterior, é possível tirar algumas conclusões.
A classe utilizada será hipotética, para demonstrar o erro, na prática. Portanto, não use esse modelo para o seu código!
class Report # não é a melhor opção, pois a função será executada # duas vezes (ou mais, quando por método). |
E como resolvemos esse caso? Assim como no exemplo anterior, basta usar o memoization.
class Report |
Depois de um processamento demorado, é possível gerar um PDF ou um XLS, por exemplo, com a mesma fonte de dados e sem divergências.
Até o momento vimos algo relativamente simples e, sim, podem existir casos mais complexos que tenham entrada de parâmetros.
Para demonstrar isso, vamos usar novamente a classe User, mas com um novo desafio: utilizar um método com parâmetro.
class User |
A função acima pode parecer completa, mas não está!
Vamos relembrar que o memoization é uma técnica de otimização que, basicamente, faz cache do primeiro resultado de uma função e retorna isso posteriormente.
Portanto, se deixarmos a função como está, não vamos ter o resultado esperado.
Veja a seguir o problema e como podemos resolver.
user = User.find(id) |
Qual vai ser o resultado se chamarmos o método, passando o :title como parâmetro, depois que já tiver chamado o mesmo método que ordena pelo campo :created_at ?
user = User.find(id) |
Sabemos agora que o memoization usado de forma errada pode nos trazer problemas.
Então vamos para a solução, que pode parecer um pouco estranha (e talvez seja mesmo), mas resolve completamente este problema.
Para isso, vamos adicionar a variável field dentro de @posts_by def posts_order_by(field: :created_at).
class User |
Agora com o código certo, vejamos como ficaria o resultado.
user = User.find(id) |
Para entender por que a função está correta, acompanhe a explicação abaixo:
class User |
A partir disso, sabemos que, se adicionarmos um novo parâmetro, provavelmente teremos um novo problema.
Porém, há uma solução para que o memoization funcione em uma função com dois parâmetros.
class User |
Alterando bem pouco do código, já podemos ter uma versão funcional com mais de um parâmetro.
Infelizmente nem tudo são flores. Temos ainda alguns “problemas” com o memoization e é exatamente quando ele executa algum processamento e retorna nil.
class User |
Caso seja preciso chamar o método last_post mais de uma vez (imaginando que nosso usuário não tenha nenhum post), o memoization não será eficiente.
Uma vez que ele vai procurar algum valor em @last_post e como está nil, ele vai executar a query todas as vezes que for chamado e é neste ponto que começamos a ter dor de cabeça com o memoization, pois temos que fazer mais checagens a fim de evitar esse problema.
Uma das soluções seria esta abaixo, mas é mais complexo de se tratar e cobrir os casos que podem retornar nil.
class User |
Mas claro, ninguém vai repetir isso em todas as funções. Nada que um helper, concern ou algo parecido não consiga resolver nesses casos.
O memoization pode ser uma ótima solução para otimizar códigos específicos, que podem ser repetitivos e longos demais. Porém, é preciso usá-lo com cautela.
Como vimos neste artigo, o memoization pode trazer alguns problemas quando usado de forma errada ou precitada.
O memoization também pode causar uma sobrecarga no armazenamento, para salvar os arquivos temporários, principalmente se usado com memória RAM.
Porém, a depender do projeto, o memoization é uma boa saída para economizar tempo e custos. Fica a seu critério (e das necessidades do projeto) usar ou não essa técnica.