Hoje a gente vai falar de um tema um pouco diferente, que é a importância de entender de infraestrutura para o desenvolvimento de softwares. No começo da minha vida como desenvolvedor, isso mais de 15 anos atrás, eu achava que o foco tinha que ser muito na sintaxe da linguagem que eu estava utilizando, em entender bem os frameworks, e tudo mais. Ao longo da minha trajetória eu fui entendendo que existiam conhecimentos satélites que eu negligenciava no processo, mas que eram extremamente importantes, fundamentais até, para fazer um software consistente, com qualidade, que não desse muita dor de cabeça em longo prazo, principalmente em questão de custo de infraestrutura. Vou dar alguns exemplos para poder contextualizar melhor o que eu estou falando e, se você é desenvolvedor, talvez você perceba a importância de ter conhecimento na área de infra para poder potencializar a qualidade dos seus softwares daqui pra frente.

Eu passei por situações de ter serviços com grande volumetria de dados ou grande quantidade de acessos, principalmente no Vigia de Preço. Nós tinhas clusters enormes, com milhões de acessos, tendo que aguentar uma BlackFriday, com 150 milhões de produtos únicos em uma base de dados com Elasticsearch. A gente entrou em uma área que pequenas alterações do fluxo da programação melhoravam significativamente a qualidade do software, o carregamento, as pesquisas e tudo mais. Aconteceram problemas com vários sistemas e subsistemas internos, tanto com banco dados e registro para armazenamento de cachê e memória, quanto problemas na CND, problemas no Elasticsearch, e esse é só um resumo dessa brincadeira. Eu sempre esbarrava no entendimento de infraestrutura e das camadas mais abstratas do sistema, por exemplo, sockets. E aí eu vou dar umas dicas que eu acho super interessantes para quem está desenvolvendo softwares de alto padrão, que tem muito acesso, muito trafego, muitos dados: A primeira coisa a se entender, que é básico da computação, mas que muitos ignoram, é que nós temos dispositivos específicos para funções específicas. Nós temos a memória RAM para armazenamento de informações curtas, que vão ser descartadas rapidamente, esse hardware tem um funcionamento muito específico, não persistente, quando a máquina reinicia, essa memória é limpa, você trafega informação na memória numa velocidade muito maior do que em outros dispositivos, então a memória serve exatamente para quando você precisa armazenar uma informação que vai ficar pouco tempo ali e que vai ser reciclada rapidamente, mas que tem que ter uma entrega muito rápida. Os SSDs e HDDs, por outro lado, têm uma entrega de escrita e leitura menor, mas têm a persistência de manter os dados registrados. E o que isso impacta quando você não tem o entendimento pleno do funcionamento desses hardwares? Na prática, a gente está programando em cima de vários níveis de abstração que, na ponta, vão acionar o CPU, a memória, o SSD da máquina, para executar as tarefas que nós estamos programando, se você não se preocupa com como está fazendo as chamadas para esses hardwares, mesmo que tenham várias camadas de abstração, você pode cometer erros muito comuns. Hoje, depois de me atentar para tudo isso, eu tenho muito menos ocorrências dentro dos meus softwares do que 5 anos atrás.

Eu vou dar exemplos de alguns dos maiores problemas que normalmente dão gargalos em grandes sistemas com muita informação e muito tráfego de dados:

Memory leak

O que é que vem a ser isso? Memory leak é quando uma máquina que está rodando uma rotina, seja um clone job ou até mesmo fazendo uma requisição, ocupa uma quantidade de memória maior do que a máquina tem disponível. Vamos supor um cenário em que você está fazendo uma migração de um XML de 1Tb, mas só tem 4Gb de memória RAM. É possível conseguir fazer esse processamento? Sim, só que você tem que utilizar técnicas para mapear esse arquivo em chunks, em pequenos pedaços, e ir descartando esse conteúdo à medida que ele é processado, porque fazer parcelamento de dados dentro da memória é muito mais rápido do que fazer direto no arquivo, por exemplo. Em vez de fazer o carregamento do arquivo inteiro, que o servidor nem vai aguentar fazer o processamento, você vai fazer a leitura de linha em linha ou de chunks, aí você vai conseguir fazer esse processamento pausadamente, em trechos curtos, e não vai utilizar tanta memória.

Eu tinha uma sub-rotina dentro do Vigia para fazer migração de arquivos XML que tinham muitos Gb dados, por exemplo, uma loja com milhões de produtos, então os arquivos sempre eram maiores do que a capacidade de memória da máquina que fazia esse processamento. A gente teve que fazer várias adaptações, tanto na rotina de migração quanto na rotina de consolidação, que foram feitas utilizando filas de processamento com repeat and kill, processando essas informações e fazendo parcels com uma rotina em Node.JS. A consolidação era feita por outro sistema, que tinha várias sub-rotinas de disparo de push e email, que precisavam ser processados de forma distribuída, se não ficava muita responsabilidade em cima de um microserviço só. Memory leak é muito comum de acontecer e prejudica bastante a performance do sistema, principalmente quando chegar ao ponto do segundo maior problema que eu vejo da performance de software.

Uso de swap

Essa também é uma prática muito recorrendo dentro de softwares que têm grande volumetria de dados. O que vem a ser uso de swap? Quando você tem um servidor padrão na AWS, na Digital Ocean, no Google Cloud ou qualquer outro, a tendência de uma máquina Linux recém configurada (não sei se esse é o padrão, mas acontecia com frequência com a gente), é que, quando está próxima ao limiar máximo de capacidade de memória, as informações começam a ser armazenadas dentro do SSD da máquina. Como o SSD tem mais espaço, justamente para não travar 100%, a máquina começa a armazenar essas informações nele. A gente sabe que a memória RAM tem uma latência muito menor que o SSD. Você pode pegar o melhor SSD do mercado e comparar com uma memória mediana, ela sempre vai entregar o resultado mais rápido, seja na consulta ou na escrita, porque ela foi feita para isso. Eu não sei se a gente vai chegar ao ponto de conseguir um SSD com o mesmo nível de performance das memórias RAM, acho difícil porque são dispositivos criados para dois trabalhos diferentes, mas pode ser que isso aconteça.

Quando o software começa a acumular, a ocupar toda a memória disponível da máquina e ativa o swap, o que acontece nesse momento? O sistema começa a ficar mais lento, porque o sistema operacional faz o nivelamento pelo dispositivo mais lento, então quando você está usando mais o SSD, ele vai abaixar a performance de tudo. Você pode notar que às vezes o seu sistema começa a importar uma grande quantidade de informação e ele começa super bem, começa rápido, e vai ficando lento ao longo do tempo, cada vez mais travado, porque ele está acumulando memory leak e utilizando swap para fazer esse armazenamento. E aí a gente chega no terceiro problema que é muito recorrente dentro dos softwares.

Garbage collection

Antigamente, nas linguagens de programação mais arcaicas, o gerenciamento de memória era feito pelo próprio desenvolvedor, você alocava e liberava memória na medida do necessário. As linguagens compiladas, tipadas, também precisam desse gerenciamento. Já os sistemas operacionais e as linguagens mais modernas foram evoluindo para ter um “coletor de lixo”, um garbage collection, que passa de tempos em tempos, reciclando espaços de memórias que não estão sendo utilizados. O ponto que demanda cuidado quando a gente trata de garbage collection é que ele pode prejudicar a performance da máquina como um todo. Quanto tem muita memória alocada e muita informação concentrada, por exemplo, se eu estou rodando uma rotina que precisa frequentemente de uma informação, essa memória acaba não sendo descartada quando o garbage collection passa. Obviamente, quanto maior a quantidade de memórias disponíveis na máquina, mais tempo o garbage collection vai demorar para limpar essa informação, porque ele precisa percorrer a memória inteira para saber quais dados estão sendo utilizados e quais não estão.

Eu vi isso acontecendo várias vezes, principalmente em desenvolvimento de jogos, ele armazena textura dentro da memória RAM ou na memória da própria placa de vídeo e, quando passa o garbage collection, dá uma travada. Os jogadores reclamam para caramba e é justamente naquele período que o garbage collection está limpando as informações que não estão sendo utilizadas. O ideal para quem precisa armazenar uma grande quantidade de informação em memória é sempre fazer a gestão e a limpeza manual desses dados e, de preferência, se estiver utilizando uma linguagem mais fortemente tipada, aí vai depender muito da linguagem que está sendo utilizando, ter uma forma de descartar aquela informação depois de processada pela função.

Se você conseguir eliminar esses três gargalos do seu sistema, memory leak, garbage colection e swap, a tendência é ter muito menos problemas numa rotina mais pesada que vai processar muita informação. Nós ainda temos outros tipos de problemas possíveis de acontecer, como, por exemplo, congestionar a rede trafegando muita informação no Redis, por exemplo. No Vigia de Preço a gente tinha uma base de dados muito grande, eram 150 milhões de produtos, e em algum momento pareceu sensato armazenar uma grande quantidade de informações de produtos dentro de um Redis hosteado, com 64Gb de memória. Só que, quando você tira essa informação de acesso direto ao banco de dados para usar um sistema de Redis sem compressão, se você tiver um pico de acesso, o Redis começa a travar. A versão free do Redis não tem cluster, você precisa usar uma instância só da máquina ou do sistema para fazer os armazenamentos. Dá para fazer um shard para ter um Redis só de leitura e um synch para fazer a escrita, isso melhora consideravelmente, você escalar ele de forma horizontal, mas aí você vai ter que pagar pela versão Interprise para poder fazer isso.

Esse mesmo comportamento acontece também no Elasticsearch e no MongoDB. No MongoDB a gente tinha shards e vários índices para fazer a leitura separada da escrita, no Elasticsearch a mesma coisa, você faz vários shards e nodes para poder equilibrar. Um problema que a gente teve foi que, quando a gente montou o Elasticsearch, o DevOps fez uma grande quantidade de shards, com pouca quantidade de memória. Olhando as estatísticas do servidor, a gente viu que o swap estava sendo utilizado de forma frequente porque, na hora de criar os índices, o Elasticsearch alocava muita memória. Foi quando um amigo meu falou para gente usar menos shards com uma capacidade de memória maior. Na época, eram umas 20 shards com 8Gb, a gente reduziu para 4 shards com 64Gb cada uma, isso deu uma mudança absurda dentro do Elasticsearch, foi bizarra a melhoria da qualidade, a performance ficou absurdamente melhor.

Tem muita coisa que a gente só aprende na prática, passando por esses problemas e resolvendo. Então entender de infraestrutura, entender de hardware, entender de sistema operacional é fundamental para conseguir performar o seu software de uma forma mais equilibrada. Entender em qual tipo de dispositivo que você vai armazenar a sua aplicação facilita a vida em vários aspectos, entender como é a arquitetura do CPU que você está utilizando dentro do seu servidor, se é Intel ou AMD, o que difere nessas arquiteturas, entender um pouco mais como funciona cada um desses hardwares, vai te facilitar e otimizar bastante a sua aplicação. Eu sei que esse não é o cenário da grande maioria, mas eu entendo que em algum momento da sua carreira esse tipo de informação pode ser necessária e você tem que se aprofundar.

Muitas vezes eu vejo os devs reclamando que o código está bem escrito, foram usadas as melhores práticas, mas o sistema está lento, não consegue performar, e estão recebendo muita reclamação. Às vezes a gente tem que dar um passo para trás e analisar onde que de fato estão os gargalos. A gente utilizava um sistema muito bom dentro do Vigia e também dentro da Uzmi, o New Relic, é um sistema um pouquinho claro, mas ele te dá um overview de gargalos de performance dentro da sua aplicação, e isso pode salvar a sua vida, principalmente quando você tem picos de acessos.

Eu poderia destrinchar muitas outras possibilidades de problemas e tudo mais, mas eu vou encerrar por aqui. Eu estou planejando fazer uma live com um amigo meu que é um puta DevOps e era meu CTO dentro do Vigia de Preço, aí a gente vai destrinchar esse tema à exaustão.

Para quem trabalha ou pretende trabalhar nessa área, vou deixar alguns posts onde eu dou mais dicas e meu parecer sobre esse assunto:

Este post é baseado no conteúdo do vídeo “A Importância De Entender De Infra No Desenvolvimento De Softwares”: