
Marcos Henrique
Dev com negócios
Como deixei meu software mais rápido com essas técnicas (Parte 1)
Um grande salto de conhecimento que tive foi quando lancei meu primeiro projeto em produção, é algo que recomendo fortemente para qualquer dev, quanto antes, melhor. Sair da zona de conforto e sofrer com o ‘na minha máquina funciona’ carrega consigo uma série de aprendizados.
Um dos principais que tive, é quando se trata de performance e escalabilidade, na nossa máquina, os testes de funcionamento da aplicação normalmente tem entre seus 10–50 registros, uns 3-4 usuários cadastrados e a mesma repetição de cliques esperados na tela para validar se está realmente funcionando.
Quando chegamos ao mundo real, essa situação muda completamente, com diversos usuários utilizando o serviço ao mesmo tempo, registros e mais registros sendo adicionados a cada minuto, e a aplicação vai ficando mais lenta e consumindo mais recursos (💸). Nesse momento vem a pergunta: ‘ué, na minha máquina funciona’. Essa é a famosa dor do crescimento, quanto mais seu software cresce, mais otimizado ele precisa ser para manter o mesmo nível de entrega.
E é sobre isso que vamos falar no post de hoje!
A primeira versão do COSTPAT (sistema de gestão patrimonial que desenvolvi) foi lançada em meados de janeiro de 2022, era uma versão bem simples, mas que cumpria com os principais requisitos dos sócios da empresa, que hoje, são meus sócios, inclusive. A partir desta data, meus problemas de performance começaram.
Após a migração de dados do primeiro cliente, que foi feita um dia antes do lançamento, acabaram notando uma lentidão no carregamento dos itens (eram mais de 1.000 bens registrados), foi então que veio o primeiro ‘ué’, quando eu acessava na minha máquina tudo estava tranquilo, mas foi aí que lembrei, meu banco de teste tinha 100 itens cadastrados. Então, no dia seguinte, eu implementei a primeira melhoria:
1 - Paginação
Eu sempre me perguntei, porque que sites tão grandes como a Americanas (onde eu adorava pesquisar brinquedos quando era criança), magazine luiza e etc, sempre tinham um rodapé com uma listagem gigantesca de páginas, que me obrigava a descer até o final para conseguir ver mais produtos do meu interesse. E foi passando por esse problema de performance que me recordei do possível motivo para isso.
Continua assim até hoje
Imagine a seguinte situação: você tem um site de venda de eletrônicos, como a Kabum, por exemplo, nela tem apenas uns 50 produtos. Por enquanto, não faz sentido colocar paginação nem nada do tipo, a experiência do usuário vai ser muito melhor vendo todos os produtos direto na tela. Mas, sua loja cresceu, agora você tem 500 produtos, entre placa de vídeo, processador e periféricos. Seu usuário, de fato, vai passar por 500 itens para escolher o que ele precisa? Provavelmente não, o mais comum é buscar pelo item e olhar os 15 primeiros resultados.
O problema de carregar esses 500 itens, é que sua aplicação, vai precisar carregá-los em tela, causando lentidão no carregamento inicial, porque o servidor vai precisar buscar do banco de uma vez e retornar como resposta, e o front-end vai precisar renderizar uma lista com esses itens, sem necessidade nenhuma, o que vai resultar não só em uma má experiência de uso (lentidão e travamentos), como também o principal, a perda de uma compra.
E é nesse ponto que entra a paginação. Ao invés de retornar uma lista gigantesca de produtos e prejudicar o carregamento da aplicação, dividimos esses 500 itens, por exemplo, em 10 páginas de 50 itens, reduzindo significativamente o tempo de carregamento da aplicação.
Foi com essa pequena mudança que consegui resolver esse problema na minha aplicação, reduzindo requisições que levavam minutos para serem resolvidas, a milissegundos.
Um ponto interessante, é que quanto mais itens existirem no seu banco de dados, mais perceptível é a diferença, digo por experiência própria, em 2023 tivemos uma adição de cliente com mais de 100.000 itens registrados no banco de dados, removi a paginação localmente para um teste, e foi extremamente surpreendente, além de atingirmos os limites de carregamento do banco de dados, pela quantidade de processamento necessário para trazer tantos itens, ainda consegui travar o navegador que estava usando para os testes. Em resumo, à medida que o volume de dados aumenta, torna-se indispensável implementar um recurso de paginação.
2 - Seleção de dados
Mesmo depois da paginação aplicada e funcionando, ainda não estava completamente satisfeito, o tempo de resposta com paginação ainda não era o suficiente para mim, queria reduzir mais para melhorar a experiência dos usuários da ferramenta (na maioria servidores públicos), foi com isso que olhei com mais rigor, as informações que estava vindo da api do sistema.
Percebi que todas as 41 colunas do banco estavam sendo retornadas para o meu frontend, entretanto, eu só utilizava 6 dessas colunas na tela onde eu chamava essa rota de listagem. Ou seja, eu trazia da api um array de itens muito maior do que o necessário, aumentando o tempo de resposta e exigindo ainda mais da conexão de rede dos usuários.
Colunas utilizadas na nova versão do software
Pensando nisso, comecei a selecionar as colunas que realmente seriam usadas, o que novamente trouxe resultados extremamente positivos de performance na aplicação, melhorando e muito o tempo de resposta, da api, ou seja, carregando os dados muito mais rápido.
Como diria o Balu do filme Mogli, o menino lobo, use somente o necessário!
O tempo foi passando, novos clientes chegando, e eu, como sempre, com uma aba do console da digital ocean aberta (provider de cloud que usamos até hoje). Um das partes onde o software COSTPAT é mais utilizado, é no inventário, etapa onde diversos funcionários das prefeituras, câmaras e etc, acessam o sistema para registrar os bens do local e identificar irregularidades para serem corrigidas posteriormente, sendo assim o momento em que o sistema mais tem acessos simultâneos (inclusive posso fazer uma publicação posteriormente sobre como lido com concorrência nesse caso). Normalmente, durante esse trabalho, o servidor chega a seus 80% de uso de CPU, junto ao banco de dados, por ocorrerem diversas buscas e escritas durante o processo, o que é plenamente normal, se formos pensar que, na época, usávamos a máquina mais barata disponível, a de 5 dólares.
Porém, comecei a constatar que muitas daquelas buscas eram repetitivas, muitos itens eram acessados +10 vezes, e não havia uma alteração de dados constante, ou seja, a resposta era a mesma todas às vezes. Então veio a ideia, como otimizar isso?
3 - Cacheamento de informações
Uma coisa que vejo muitos cursos fazendo erroneamente é dar a entender a nós desenvolvedores que cache deve ser usado para tudo e vai resolver todos os problemas de performance de uma aplicação, mas no mundo da programação, uma escolha pode gerar diversos resultados positivos, mas, em contrapartida, também traz uma série de efeitos colaterais.
Quando se trata de cache, o principal benefício é reduzir a quantidade de requisições a um recurso, que pode ser do back-end acessando o banco de dados, por exemplo, ou até mesmo do front-end acessando o back-end. No caso do COSTPAT, cachear somente no front-end não iria resultar em uma grande diferença, já que os acessos aos recursos eram de usuários diferentes no mesmo item, ou seja, se cada um abrir o item 02 em seus dispositivos, ainda serão 2 requests para buscar o item.
A solução, então, foi cachear esses dados no back-end, aproveitei para cachear não somente os dados dos itens mais buscados, mas também a primeira página dos patrimônios e produtos cadastrados, tornando a experiência do usuário bem mais rápida ao entrar pela primeira vez nessas páginas.
Para implementar seria bem simples, né? Adicionar o redis (banco key-value em memória que escolhi, por justamente ser muito rápido para trazer as informações e conseguir provisioná-lo no próprio servidor) e salvar os itens lá dentro com um tempo de expiração de, por exemplo, 5 minutos. Porém, o principal problema de cache é a invalidação do mesmo.
Enquanto houver um cache, esse seria o funcionamento descrito acima, os dados do item 01 seriam trazidos pelo redis. Mas, vamos imaginar a seguinte situação, se, durante os 5 minutos que o cache está ativo, um usuário 03, decidir atualizar o item 01, o que aconteceria? Os outros usuários continuariam usando o Redis, e a informação estaria desatualizada até a expiração do cache.
Para resolver esse problema, precisamos criar uma lógica de invalidação do cache, ao modificar o item, ir ao redis e deletar a chave. Assim, quando um usuário buscar novamente, primeiro a busca vai ao banco de dados, pegando o valor mais atualizado, e depois, atualizando o cache com o novo valor.
Os problemas do uso exagerado de cache na minha visão são:
- Em alguns casos, os dados não podem estar desatualizados, como um saldo bancário, por exemplo.
- Em outros casos, a lógica de invalidação fica complexa demais, onde o item é muito mais alterado que lido, e o cache acaba nem sendo muito usado no fim das contas, precisando ser invalidado em diversas partes do código.
Por isso, o mais adequado é analisar a real necessidade de aplicar um cacheamento na sua aplicação, principalmente no back-end, que afeta diretamente todos os usuários dentro daquele escopo (no meu caso, uma prefeitura). No front-end, esse risco é menor, já que afeta apenas um usuário específico, e é normalmente aplicada uma técnica de revalidação enquanto mostra o cache salvo para o usuário (podemos abordar isso em uma possível parte 2)
No caso dessa funcionalidade do COSTPAT específica, valia muito a pena utilizar, iria economizar muito recurso do banco de dados e principalmente, muito dinheiro 🤑.
Essas foram as 3 principais mudanças que fiz na primeira versão do COSTPAT, e que me fizeram economizar muito em todos os sentidos, obviamente ainda tenho muita história para contar, em 2024 tomei a decisão de refazer completamente o COSTPAT, para torná-lo mais performático e simples de utilizar, e confesso que não me arrependi nem um pouco, foram meses de trabalho que geraram um resultado excepcional.
Se tem interesse em conhecer as outras 5 técnicas que utilizei nessa segunda versão e que proporcionaram o sistema a dobrar a quantidade de usuários e dados registrados com o mesmo custo, comente aqui embaixo 🙂.
Por hoje é só pessoal, até a próxima!