Executando verificação de segurança...
15

A lentidão no legado que resolvi sem resolver

Eu tinha um cliente que atendia há alguns anos. Os serviços só envolviam pequenas alterações de layout no site da empresa. Porém, dessa vez ele me procurou com um problema diferente.

Eles estavam com uma lentidão no sistema que chamavam de Gerenciador. Acessei para verificar e realmente estava bem lento. Levava cerca de 1 minuto para carregar qualquer página.

Conhecendo o terreno

O sistema era um misto de CMS com CRM. Servia para fazer algumas alterações no site da empresa e também para gerenciar contatos e clientes.

O problema era que eu não conhecia nada do sistema. Não tinha sido desenvolvido por mim, eu nunca tinha nem visto o código. Era escrito em PHP, linguagem que eu não mexia há anos. Usava o framework Zend, que eu só conhecia de nome. E tinha zero versionamento, documentação e testes, pelo menos que eu soubesse.

Tudo o que eu tinha era o acesso à hospedagem, que utilizava o cPanel, e ao servidor FTP.

Outro “problema”, agora pessoal, é que adoro investigar esse tipo de gargalo nas aplicações. Então, lá fui eu, na cara, coragem e vontade de me incomodar e aceitei o projeto.

Metendo a mão na massa

A primeira coisa que fiz foi baixar todo o código via FTP e colocar em um repositório Git.

O próximo passo foi tentar rodar o projeto localmente. Não me recordo exatamente como foi essa etapa, mas lembro que eu não consegui rodar localmente. Na época não existiam o ChatGPT e o Claude Code para ajudar.

Então minha próxima tentativa foi adicionar alguma forma de observabilidade ao sistema diretamente em produção. Felizmente, o cPanel tinha uma integração com o New Relic e o plano gratuito desse era o suficiente para o volume de transações do sistema.

Consegui adicionar a ferramenta sem mexer no código, ou seja, menos risco de quebrar algo.

Deixei o New Relic rodando uns dias e voltei para conferir.

Diagnóstico

O New Relic me mostrou exatamente onde estava o problema. Era um clássico N+1.

Investiguei o código e descobri que havia uma consulta no banco de dados para buscar todas as mensagens não lidas. Logo depois havia um loop : para cada mensagem fazia outra consulta para buscar mais dados.

Esses dados eram utilizados para popular um ícone no cabeçalho do sistema, presente em todas as páginas, com o número de mensagens não lidas.

Enquanto o sistema tinha 10 ou 100 mensagens não lidas, o impacto era pequeno, porém, quando esse número cresceu para milhares, o impacto ficou significativo e o que levava milissegundos passou a levar segundos ou até minutos.

Após entender o problema, eu precisava de uma solução simples que não precisasse de muitas mudanças no código, dada a sua fragilidade.

Solução

Optei por adicionar um limite ( LIMIT do SQL) na query que buscava as mensagens não lidas no banco de dados. Agora, ao invés de retornar todas, retornava somente 100. O ícone que antes mostrava o número total de mensagens passou a mostrar “99+”.

Você pode estar se perguntando “mas isso não resolve o N+1, resolve?” e a resposta é “não, não resolve”, — até onde sei, pode ser que o N+1 esteja até hoje em produção — mas resolve a lentidão e por consequência o problema do usuário.

Eu poderia ter alterado a query para buscar todos os dados de uma só vez e resolver o N+1? Com certeza, mas o risco de eu quebrar algo no meio do caminho seria muito maior.

O usuário final não está interessado se o código faz 1 ou 100 consultas ao banco de dados. O que importa é a usabilidade e, nesse caso, as 100 consultas não comprometiam o sistema.

Não me entenda mal. Não estou advogando entregar código com problemas de performance, mas cada situação tem seu contexto. Esta foi a melhor solução possível? Não, porém, considerando as limitações, foi boa o suficiente. Lembre-se de que eu estava mexendo em um projeto desconhecido, sem ambiente de desenvolvimento e sem possibilidade de testes.

Solução pronta. Mas e agora? Como faz para validar e implantar?

Implantação

Eu só tinha uma forma de validar minha solução: testar em produção (evitem fazer isso em casa!).

Escolhi uma hora fora do horário comercial; abri ambas as versões (antes e depois das minhas modificações), caso houvesse algum problema, eu poderia reverter rapidamente; abri o sistema no navegador; e finalmente abri o FTP e enviei os arquivos modificados.

E não é que funcionou?

O sistema estava abrindo em um tempo aceitável. O ícone mostrava “99+” mensagens não lidas.

Comuniquei ao cliente e continuei monitorando com o New Relic por mais alguns dias para garantir que o problema estava resolvido e nenhum outro havia aparecido.

Sucesso!

O que eu faria diferente?

Olhando para trás, eu faria algumas coisas diferentes.

Código sem testes; desenvolvimento sem conseguir rodar localmente; validação em produção; implantação via FTP. Essas foram práticas, no mínimo, questionáveis.

Não vou dizer que não repetiria algumas das mesmas práticas, se necessário. Em empresas onde software não é a atividade-fim, às vezes, o código rodando em produção é tudo o que se tem.

O que eu com certeza faria diferente é que, atualmente, com mais maturidade, eu me preocuparia muito mais em gerenciar os riscos e principalmente comunicá-los ao cliente. Deixaria clara a situação do projeto e os riscos envolvidos. Ofereceria diferentes opções para lidar com o problema e seus respectivos prós e contras.

Outra coisa que faria diferente é a precificação. Cobrei um valor fixo para resolver o problema, assumindo todo o risco do projeto. Atualmente, eu cobraria um valor por hora, dividindo o risco.

Aprendizados

Podemos tirar algumas lições dessa história. A primeira é que não existe software que não precise de manutenção. Esse sistema específico rodou tranquilamente por anos até que, de repente, um problema que sempre esteve ali o tornou praticamente inutilizável.

A segunda lição é o valor de uma boa observabilidade. Adicionar o New Relic ao projeto me possibilitou encontrar facilmente o problema em meio a milhares de linhas de um código desconhecido, em uma linguagem e framework com os quais tenho pouca experiência.

A terceira lição é que a melhor solução depende do contexto. Em um mundo ideal, eu teria refatorado a query para eliminar o N+1. No mundo real, com código legado, sem testes e sem ambiente local, adicionar um simples LIMIT foi a decisão mais inteligente.

Conclusão

No fim, deu tudo certo. Consegui resolver o problema do cliente e o desafio foi bem legal. Gosto bastante de investigar e resolver esse tipo de problema de performance.

E aí, o que achou da solução? Você teria arriscado resolver completamente o N+1 ou também teria ido pelo caminho mais conservador? Já passou por alguma situação similar? Comente aqui e vamos conversar.

Você está com algum problema desse tipo no seu projeto? Às vezes, alguém vendo o problema de fora pode ajudar. Me mande uma mensagem contando um problema técnico que você está enfrentando e quem sabe a próxima newsletter é contando como eu te ajudei.

Carregando publicação patrocinada...
2

Cara, tenho um relato semelhante e relativamente recente, de cerca de 1 ano e meio atrás. O site da empresa onde atuo foi desenvolvido por uma empresa terceirizada alguns anos antes de eu entrar. Curiosamente, também foi feito em PHP, utilizando o October CMS (para mim, uma das piores ferramentas de PHP com as quais já tive contato, é como uma mistura de Laravel com WordPress, mas com uma comunidade e documentação extremamente fracas).

Tivemos que fazer uma migração, pois íamos atualizar a versão do PHP, do banco de dados e do servidor. Como já não tínhamos mais contrato com a empresa terceirizada, a manutenção ficou por nossa conta. Na época, o October já estava na versão 3, mas nosso site ainda rodava na versão 1 (nunca pensei que duas versões de diferença pudessem me causar tanta dor de cabeça).

Alguns problemas encontrados

  • Documentação extremamente fraca;
  • Comunidade muito pequena e pouco ativa; mesmo em fóruns, as soluções geralmente eram para Laravel e nem sempre se encaixavam na situação;
  • Arquivos inexistentes, parecia que alguns nunca tinham sido criados; eles não atualizavam versões, apenas “remendavam” o site;
  • Plugins descontinuados e sem documentação.

Na época, éramos eu, meu coordenador e um estagiário. Meu coordenador não sabia mexer no site, e o estagiário (meu amigo do curso técnico) ajudava bastante na parte de servidores, mas em desenvolvimento a responsabilidade era toda minha. Por conta de outras demandas, acredito que eu levei uns 3 a 4 meses nesse processo. No início eu não fazia ideia de como prosseguir: estava completamente perdido, lidando com páginas que simplesmente não funcionavam e sem indicar onde estava o erro.

Não lembro de todos os detalhes técnicos, mas basicamente saí do zero para resolver o problema. Tive que recriar arquivos, ajustar o composer, usar Docker com diferentes versões do PHP para ir atualizando o site, tudo isso mantendo um backup da versão antiga que “ainda funcionava”. A cada atualização surgia um erro novo, em um lugar diferente.

No final, o site ficou no ar e funcionando “por aparelhos”. Não vou mentir: no fim eu nem sei exatamente como ele está rodando, alterei tanta coisa que parece um milagre estar funcionando. Mas está. A manutenção continua sendo péssima, e eu acredito fortemente que refazer o projeto seria a melhor opção, seja em Laravel ou WordPress, já que, para mim, o October é muito fraco.

A conclusão que tirei é: mesmo que você não saiba como resolver um problema, em algum momento vai encontrar a solução, só precisa ter paciência e boas xicaras de café.

1

Curiosamente, também foi feito em PHP

Pois é, PHP tem muito disso. Acredito que pela baixa barreira de entrada. É uma faca de dois gumes.

No meu caso mesmo, foi a primeira linguagem que tive contato. Sem nem saber direito o que era programação, eu já fazia pequenas alterações em um site em produção.

October CMS (para mim, uma das piores ferramentas de PHP com as quais já tive contato, é como uma mistura de Laravel com WordPress, mas com uma comunidade e documentação extremamente fracas).

Nunca tinha ouvido falar desse October CMS.

nunca pensei que duas versões de diferença pudessem me causar tanta dor de cabeça

Já vi esse mesmo problema no AngularJS. Cada versão era uma dor de cabeça para migrar. Não sei como está atualmente.

A conclusão que tirei é: mesmo que você não saiba como resolver um problema, em algum momento vai encontrar a solução, só precisa ter paciência e boas xicaras de café.

Exatamente. Sempre tem uma solução. A pergunta é se vale a pena investir nela.

1

Show de bola demais, estranhamente passei por isso em um dos meus trabalhos anteriores, o contexto era quase que idêntico ao seu. Na época o problema era com alguns relatórios, por conta da massa de dados o tempo de espera era muito longo.

O que optamos por fazer foi reescrever as queries e assumir a culpa/risco de quebra, mas a sua solução pro seu problema, dado o contexto é genial.

0