Memory Leak

Leon balloni
TC - Tecnologia e Produtos
6 min readMar 1, 2023
Fonte: https://engineering.cartona.com/solving-memory-leaks-issues-in-ionic-angular/

Vazamentos de memória são incrementos contínuos de uso de recurso em aplicações dos mais diversos tipos sem sinal explícito de estabilização e/ou redução de seu uso ao longo do tempo. Em termos gerais, é a má alocação ou gestão (Leitura, escrita e liberação) da memória disponível para aplicação e isso pode gerar diversos problemas, tendo como principais: O desperdício de recurso computacional, a insustentabilidade da aplicação no longo prazo, além de quebras e reinicializações (indisponibilidade) dela.

Há outros aspectos de memory leak os quais não vou abordar aqui para diferentes contextos como segurança da aplicação, web apps e mais. Um comportamento característico de possível memory leak é o gráfico em formato de serra onde existe um acúmulo, queda e acúmulo contínuo com uma inclinação na curva histórica tendendo para alta conforme visual abaixo:

Fonte: Autor

Obs: não necessariamente isso é um memory leak, mas é um bom indicativo de que é necessário observar a aplicação.

Exemplo clássico mais perceptível:

Fonte: https://en.wikipedia.org/wiki/Memory_leak#/media/File:Sample_sawtooth.jpg

Como podem ocorrer?

Há diversas formas e algumas que tive oportunidade de identificar envolvem a estrutura de dados, o conjunto de pacotes/bibliotecas utilizadas e funções ou métodos que alocam espaço em memórias e, por meio de operações internas, perdem os seus ponteiros sem antes removê-los.

Estrutura de dados

Há estruturas utilizadas para armazenar, controlar e gerir os dados na aplicação, caso não sejam bem avaliados, podem ocasionar problemas de memory leak. Isso ocorre por causa de ponteiros perdidos antes de liberar a memória e por acúmulo contínuo de dados nas estruturas que vão alocando cada vez mais memória, em muitos casos de forma desnecessária, durante sua execução.

É interessante reavaliar cada estrutura de dados elaborada a fim de entender se houve a alocação de maneira desnecessária e algumas características que podem gerar isso envolvem o uso inadequado de funções/métodos, atributos além da dificuldade de interpretação e verificação do código devido ao acoplamento e coesão.

Pacotes e bibliotecas

Bibliotecas são facilitadores das atividades a serem executadas diariamente. Contudo, sua vantagem também pode se tornar uma desvantagem, pois, o encapsulamento e abstração ocultam complexidades ao mesmo tempo que podem impedir ou dificultar identificar os pontos de uso que geram o memory leak na aplicação.

Nessas situações, é recomendável buscar os responsáveis pela sustentação do pacote e direcionar a dificuldade para quem tem maior facilidade de uso e manutenção (em outras palavras, bom e velho github para os responsáveis, stack overflow para os aventureiros, etc).

Estudo de caso

A situação passada no time de Dados envolvia o nosso modelo de NLP desenvolvido em Python que faz uso de redes neurais profundas a fim de consolidar vários modelos de classificação que tornam a vida de nossos usuários e funcionários mais fácil e ágil.

Dentre algumas de suas etapas envolvia a disponibilização da solução via uma API que poderia atender os mais diversos serviços dentro da empresa. Contudo, no desenrolar dessa atividade e nos testes realizados notamos que a aplicação recaia em acúmulo crescente de memória sem indicativo de queda.

Seguindo a metodologia de estudo de caso, os seguintes passos foram definidos para facilitar nosso estudo e resolução do caso:

  1. Descrição minuciosa do problema e investigação de diversas situações similares, tanto em questão de codificação, quanto dos pacotes utilizados;
  2. Elaboração de etapas de verificação e conferência do material coletado a fim de verificar em qual caso nosso problema melhor se encaixava, e;
  3. Execução e implantação das potenciais correções.

Abaixo descrevo as etapas mencionadas em maiores detalhes.

Identificação da fonte de vazamento

Seguindo a proposta acima, realizamos uma análise minuciosa da estrutura de dados e não identificamos vazamentos. Algumas bibliotecas foram utilizadas para identificar os pontos de vazamento como memory-profiler (perfilamento do consumo de memória e alocação acumulada por cada método) e objgraph (identificar os objetos, atributos,métodos e dados gerados a partir do item avaliado).

fonte: https://en.wikipedia.org/wiki/SpaCy#/media/File:SpaCy_logo.svg

Ao avaliarmos os resultados do perfilamento e simulamos situações de reinicialização do pacote spacy, essencial para execução do modelo, que foi a principal causa de vazamento. Para resolução (mitigação do problema), foram testadas duas alternativas:

  • Recarregar o modelo on the fly: não recomendado devido a indisponibilidade e por não reduzir o uso de memória ao nível inicial. Mais sobre aqui
  • Uso de uma estrutura de API que faz uso de preloading e forking como forma de não copiar e alocar a memória da app, evitando o desperdício da memória e assegurando que os processos que garfaram o principal não alterem a memória do mestre. Mais sobre aqui

Para essa estrutura operar apropriadamente, é necessário que os dados principais da aplicação estejam gerenciados fora da aplicação. Isso se dá devido a propriedade característica do compartilhamento de memória via preload que é a importação dos dados via o mestre. Se durante o processo de cada criança que copiou os dados do mestre ocorrerem alterações significativas nos dados utilizados, haverá uma discrepância no resultado gerado, tornando a aplicação no mínimo ruim e no máximo completamente inútil.

Fonte: Autor e http://zarnovican.github.io/2016/02/15/uwsgi-graceful-reload/#pre-fork-vs-lazy-app

Os SGBDs, sistemas de storage ou outrem como BigQuery, são alternativas para o armazenamento dos dados e, em muitos casos, são uma boa prática a fim de separar a função de uso dos dados de seu armazenamento e gestão. Em particular, numa arquitetura moderna em cloud, o Bigquery tem diferencial considerável que vale a pena conhecer através desse link.

Fonte: Autor

Note como a oscilação ocorre mas não indica uma tendência crescente de aumento no consumo de memória.

É interessante destacar que o processo de alocação de memória é parte de toda aplicação e, caso ela ocorra de maneira apropriada, essas oscilações são esperadas, todavia nunca numa tendência crescente permanente que represente um risco para própria sobrevivência da aplicação. Apresentação no suporte da página do pacote com a solução: Sobre a issue

Outros casos

Há situações onde a própria segurança da aplicação está em jogo, seja por causa da crescente de uso de memória ou por indivíduos mal interessados que querem explorar isso para derrubar e gerar instabilidade na aplicação ou explorar essa vulnerabilidade quando a aplicação está com baixa capacidade de memória. A OWASP tem um artigo dedicado a explicitar essas diferentes situações: Security issues

Estrutura de dados, dependendo da complexidade, podem ser fonte de grande dor de cabeça na hora de evitar vazamento de memória, é interessante conferir a dependência de cada objeto e como é armazenado essa informação. Ademais, adicionar métodos destrutores é uma boa prática para eliminar instâncias após utilizá-las.

Linguagens como Python possuem o Garbage Collector que faz automaticamente esse processo de destruição, contudo, nem sempre isso ocorre com sucesso seja devido à objetos que não são eliminados e/ou processos que não são apropriadamente finalizados durante a execução e ai cai a necessidade de entender mais a fundo o que a mão invisível do código fonte deveria fazer e não foi capaz.

Concluindo

Há uma infinidade de casos diferentes de vazamento de memória e dificilmente serei capaz de descrever todos e muito menos com a devida qualidade. Fora isso, certamente é passível de ter sofrido de memory leak e nem soube o porquê ou como e por isso espero ter ajudado, nem que de certa colaborado para aquele breve momento de epifania que todo desenvolvedor possui depois de horas de pesquisa.

--

--