Diagnosticar vazamentos de memória em uma aplicação pode ser um desafio complexo, mas uma análise cuidadosa dos dados de monitoramento pode revelar a origem do problema. Em um cenário de demonstração, podemos simular um vazamento de memória e analisar os sinais de alerta fornecidos por ferramentas como o OpenTelemetry e Pyroscope.
Primeiramente, para identificar um possível vazamento de memória, é necessário habilitar a funcionalidade de cache recomendada no aplicativo de demonstração. Após ativar essa configuração e rodar a aplicação por aproximadamente dez minutos, será possível observar a coleta de dados necessários para análise. O primeiro passo para diagnosticar qualquer problema é verificar se ele realmente existe. Para isso, podemos usar ferramentas de monitoramento como o Grafana, que fornece dashboards detalhados sobre o desempenho da aplicação.
No caso da aplicação de demonstração, existem dois painéis de controle. O primeiro é dedicado ao monitoramento do OpenTelemetry Collector, enquanto o segundo exibe gráficos que analisam a latência e a taxa de requisição de cada serviço. Entre as métricas exibidas, destacam-se as relacionadas aos serviços recomendados, o uso de CPU e memória, a latência dos serviços e a taxa de erros. A análise dessas métricas pode revelar comportamentos anormais, como picos de utilização de CPU e latência prolongada nas requisições, além de um aumento no uso de memória de serviços específicos.
O vazamento de memória, em termos simples, ocorre quando a memória utilizada por um processo aumenta continuamente, sem ser liberada de maneira adequada, o que eventualmente leva a picos de CPU. Se o vazamento persistir, a memória eventualmente atingirá seu limite, forçando a execução contínua de coletas de lixo (garbage collection) e, como resultado, gerando mais picos de CPU. Para aprofundar a análise dos problemas relacionados à memória e CPU, é importante utilizar perfis, que são ferramentas poderosas para observar detalhadamente o comportamento dos serviços sob carga.
Caso a causa raiz esteja relacionada ao processo de coleta de lixo, é útil comparar os perfis de casos normais e anormais para identificar o método responsável pelo vazamento. Este procedimento pode ser feito aplicando perfis em serviços específicos, como o serviço de recomendações da aplicação de demonstração. O serviço de recomendação, ao ser monitorado, gera métricas, rastros e perfis, permitindo uma análise mais profunda dos comportamentos de memória e CPU.
O Jaeger, outra ferramenta de monitoramento, pode ser utilizado para buscar os rastros (traces) e exibir a latência de cada requisição. Caso a latência aumente nas requisições do frontend, é possível filtrar os rastros para incluir apenas aqueles que se referem aos serviços de recomendação. Ao ordenar os rastros pela latência, podemos rapidamente localizar aqueles que tomaram mais tempo para serem processados. A partir daí, é possível verificar detalhes sobre o que está ocorrendo, como o atributo app.cache_hit, que indica se o serviço utilizou ou não o cache. A diferença de latência entre as requisições com cache (onde o valor app.cache_hit=true) e as sem cache (onde app.cache_hit=false) pode evidenciar a origem do problema.
A análise de causa raiz pode ser feita de diversas maneiras, mas três abordagens principais se destacam. A primeira é usar dashboards interativos, como aqueles oferecidos por ferramentas de observabilidade comerciais, que permitem analisar gráficos de serviços e polystats. A segunda abordagem envolve o uso de SQL para consultar dados estruturados armazenados no Promscale, uma base de dados que facilita a análise mais detalhada dos sinais de métricas, rastros e logs. A terceira abordagem, voltada para grandes escalas, consiste em automatizar a análise utilizando aprendizado de máquina (machine learning), o que permite aprimorar a precisão da análise conforme o volume de dados cresce.
Ao considerar a implementação de uma solução de observabilidade mais complexa, especialmente em grandes escalas, é recomendável um sistema que permita não só visualizar os dados em tempo real, mas também realizar análises em profundidade usando SQL. Para soluções menores e mais simples, a análise visual dos dados, como as telas de gráficos, pode ser suficiente.
Uma vez que a análise de rastros é realizada, o uso de perfis pode ser uma forma eficaz de aprofundar ainda mais na identificação de falhas. O Pyroscope, por exemplo, é uma ferramenta de perfil que pode ser integrada ao OpenTelemetry para fornecer informações detalhadas sobre o uso de recursos das aplicações. O Pyroscope coleta perfis de memória e CPU para serviços implementados em diferentes linguagens, como Python e Golang. A coleta pode ser feita por dois métodos: o método pull, que é simples de implementar e coleta os perfis periodicamente de um endpoint, e o método push, que é mais flexível e permite uma instrumentação manual usando a API do Pyroscope.
O Grafana, além de ser uma ferramenta de visualização poderosa, também suporta a exibição de gráficos de chama (flame graphs), que ajudam a entender onde estão concentrados os problemas de performance dentro de uma aplicação. Grafana pode coletar e analisar perfis de diferentes linguagens de programação, incluindo Java, Python e Golang, sem a necessidade de modificar o código-fonte da aplicação.
Ao aplicar perfis no serviço de recomendação, por exemplo, pode-se observar a interação entre os recursos da CPU e a memória. Modificando e implantando novamente o serviço, com a configuração adequada do Pyroscope, é possível obter uma visão clara das mudanças nos recursos consumidos pelo serviço. No entanto, vale destacar que, atualmente, o perfil de memória no Python ainda apresenta limitações, o que pode restringir a análise de vazamentos de memória para algumas linguagens específicas.
Além das métricas e ferramentas mencionadas, é importante lembrar que uma análise precisa e eficaz de problemas de performance e vazamentos de memória exige um entendimento claro de como cada ferramenta de observabilidade funciona em conjunto. Não se trata apenas de identificar os sintomas do problema, mas de aplicar uma abordagem integrada que permita correlacionar diferentes fontes de dados (métricas, rastros, logs, perfis) para obter uma visão completa e precisa da aplicação.
Como Propagar e Transferir Contexto de Traces em Serviços Gerenciados e Microserviços
A propagação de contextos de trace entre diferentes camadas de sistemas, como serviços gerenciados e microserviços, representa um desafio significativo quando se lida com soluções de observabilidade comerciais que não utilizam trace distribuído. Em muitos casos, o contexto de trace não segue o formato convencional de spans, sendo tratado separadamente. No entanto, é possível estabelecer uma conexão entre o RUM (Real User Monitoring) e o trace distribuído associando o ID interno do RUM ao trace ID. Essa abordagem é útil, mas em sistemas mais complexos, pode ser difícil integrar diferentes tecnologias, como servidores API gerenciados e microserviços, ao processo de tracing.
Normalmente, a geração de spans ocorre de maneira previsível a cada intervalo, seja em RUM, servidores de API ou microserviços. No entanto, em soluções comerciais de observabilidade, o que se observa é que nem todos os sistemas geram spans; é comum que o trace tenha início nos microserviços. Isso ocorre, por exemplo, quando o formato do trace varia entre as diferentes tecnologias, o que pode dificultar a implementação da propagação e transferência do contexto de trace. Se, por exemplo, os servidores API usam B3 e os microserviços utilizam W3C, a propagação do trace será impossível sem a utilização de um propagador adequado.
Em casos de falha de propagação, um novo trace pode ser criado para cada segmento, mas isso resulta em uma desconexão do trace original, tornando a análise mais confusa e difícil de entender. É comum que um único trace ID seja composto por múltiplos spans, mas se o trace ID for dividido em múltiplos IDs separados, a análise de contexto se torna mais complexa, especialmente para equipes de Site Reliability Engineering (SRE).
As limitações de observabilidade em serviços gerenciados são um ponto importante a ser considerado. Quando ocorre uma falha no nível de recursos do sistema, é difícil determinar a causa raiz sem uma observabilidade robusta. Portanto, a solução passa pela criação de estratégias próprias para suprir essas lacunas de visibilidade. Isso é especialmente importante quando se trabalha com plataformas como AWS, GCP ou Azure, onde a implementação de tracing precisa ser cuidadosamente planejada para garantir a continuidade da visibilidade do trace entre as diferentes camadas de serviço.
Demonstração de Propagação de Trace com AWS CloudFront
Um exemplo prático de como isso pode ser implementado é o uso do AWS CloudFront, uma rede de distribuição de conteúdo (CDN), para conectar servidores de API e outros serviços gerenciados. O CloudFront utiliza o padrão B3 para propagação de trace, enquanto os microserviços, como os desenvolvidos em Spring Boot, podem utilizar o OpenTelemetry para instrumentação automática.
A implementação do trace começa quando o cliente faz uma requisição ao CloudFront, que cria um novo trace ID e span ID. Este contexto de trace é propagado para os microserviços, que, utilizando o propagador OpenTelemetry, convertem o trace ID de B3 para o formato W3C. A partir desse ponto, o trace pode ser transferido para o OpenTelemetry collector e, finalmente, para sistemas de observabilidade. A interação entre os sistemas gerenciados, como CloudFront, e as ferramentas de observabilidade torna-se mais eficiente através da configuração adequada dos propagadores, garantindo que o contexto de trace seja mantido durante a jornada das requisições.
Contudo, é importante observar que o CloudFront, por si só, não oferece suporte nativo para OpenTelemetry, embora suporte nativamente o AWS X-Ray. Isso cria a necessidade de implementar um trace de ponta a ponta (E2E) com OpenTelemetry, se o objetivo for medir latências e erros desde o cliente até o servidor. Essa implementação exige o uso de um propagador configurado adequadamente para a comunicação entre os diferentes padrões de trace.
A Utilização de Propagadores para Resolver a Falha de Propagação
O uso de propagadores torna-se essencial para lidar com os desafios da propagação de contexto de trace entre sistemas que seguem padrões diferentes. Se o trace ID gerado pelo CloudFront estiver no formato B3 e os microserviços utilizarem o W3C, o propagador servirá como intermediário para garantir que o contexto do trace seja corretamente transferido entre os sistemas.
Além disso, em sistemas baseados em funções serverless, como os lambdas da AWS, é possível desenvolver lógica personalizada para transferir o contexto de trace por meio de logs. Quando um novo trace ID é gerado, o sistema pode registrar este ID em um log e, em seguida, transferir esse contexto para o backend de observabilidade. Esse processo é facilitado pela capacidade de configurar e ler os logs para extrair o contexto do trace e enviá-lo para a infraestrutura de monitoramento.
Mesmo que alguns serviços gerenciados, como o CloudFront, não ofereçam suporte total para trace distribuído, é possível contornar essas limitações usando o OpenTelemetry com scripts personalizados que configuram os cabeçalhos necessários para a propagação do trace. A capacidade de criar e transferir o contexto de trace entre diferentes serviços é um aspecto fundamental para garantir a integridade dos dados de observabilidade.
Considerações Adicionais sobre Observabilidade em Serviços Gerenciados
Em sistemas mais complexos, a observabilidade de serviços gerenciados deve ser vista não apenas como uma questão de configuração de tracing, mas também de design. A falta de visibilidade pode se tornar um problema sério quando falhas ou latências não podem ser diagnosticadas rapidamente. Por isso, é necessário desenvolver a aplicação com estratégias de observabilidade próprias, como o uso de logs detalhados que ajudam a identificar falhas e estabelecer a continuidade do trace entre sistemas.
Além disso, as técnicas de propagação e transferência de trace devem ser sempre integradas com o entendimento de como os serviços gerenciados funcionam internamente. Dependendo da plataforma em questão (AWS, GCP, Azure), as ferramentas e métodos de instrumentação podem variar, o que exige uma adaptação constante às boas práticas da plataforma e à evolução dos frameworks de observabilidade.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский