#2 Como aperfeiçoar a velocidade de seu site: back-end

Em meu post anterior, falei de dois tópicos de suma importância quando o assunto é deixar nossos sites mais rápidos: latência e front-end. Hoje, falarei de outro item importantíssimo: back-end!

Enquanto a maioria dos problemas de carregamento atuais são culpa de a) sites hospedados no exterior e b) sites com uma tonelada de recursos (plugins, imagens, estilos, vídeos, etc), existem muitas coisas que ainda podemos fazer nos “bastidores” do nosso site para torná-lo um canhão.

Claro que estas alterações no lado do servidor variam de plataforma para plataforma, de linguagem de programação para linguagem de programação. Dicas de performance para C# podem variar enormemente das dicas para PHP, o mesmo para Java vs. NodeJS, só para citar alguns exemplos.

Sendo assim, tentarei focar em conceitos mais genéricos e abrangentes, sem entrar no detalhe de “programming hacks” e tunning de aplicações na linguagem X ou Y. Além disso, muito provavelmente, as dicas deste post farão mais sentido para desenvolvedores de sistemas web, e não para desenvolvedores de sites, uma vez que backends complexos geralmente não são necessários em sites comuns.

Dica 1: Mantenha seu projeto atualizado

Você costuma acompanhar as release notes (também chamado de changelogs) da sua linguagem de programação favorita?

Nas release notes é que os engenheiros responsáveis pelas linguagens dizem o que foi alterado de uma versão pra outra. E, se tem um item muito recorrente, são as melhorias de performance. Sempre é comum ver tanto melhorias gerais na performance, quanto melhorias em pontos específicos, que estavam com problemas de desempenho.

Um exemplo é esse changelog do PHP 7.

Procure pela palavra performance que você verá, na data deste post, três menções a ajustes de performance.

Ou seja, estando atualizado com a sua linguagem de programação, seu sistema opera com o melhor desempenho básico possível. Claro, grandes mudanças de versões (geralmente no primeiro ou segundo número da versão, dependendo da linguagem) podem acarretar em um estudo mais aprofundado para fazer a mudança, pois, às vezes, alguns comandos somem e outros tomam o seu lugar, bem como configurações de ambiente.

Dica 2: Deixe parte da responsabilidade com o front-end

“Como assim, no post anterior você não falou que 80% do tempo de carregamento das páginas é culpa do front-end? E agora quer que eu coloque mais coisas lá?”

Sim. E não!

Usar o front-end para processar algumas coisas, as coisas corretas, faz muito bem à performance geral da sua aplicação. Com a escala da web atual, você jamais conseguirá ter um servidor com todo o poder suficiente para atender aos seus usuários rapidamente processando tudo nele. Você terá de aprender a usar tecnologias mais responsivas, assíncronas e não-bloqueantes, como Ajax e Web Sockets, para dar ao usuário o que ele deseja, na hora que ele deseja e sem sobrecarregar seu back-end.

Scripts de construção de tela são um ótimo exemplo. Geralmente podemos pedir ao servidor apenas os dados crus, coisa que o client-side não consegue resolver sozinho, e com estes dados montar a apresentação da tela, sem a necessidade da mesma ser construída no servidor.

Outro exemplo são os scripts de validação no lado do cliente. Óbvio que não estou dizendo para confiar apenas no cliente, mas uma pré-validação no lado do cliente pode poupar idas desnecessárias ao servidor, por exemplo.

Não estou querendo aqui te convencer a abrir mão de processar coisas no servidor, longe disso. Estou apenas sugerindo que, o que puder ser feito no lado do cliente, sem que atrapalhe a experiência dele, pode ser feito. Afinal, geralmente o browser do usuário está com mais processamento ocioso do que nossos servidores.

Dica 3: Publique para produção

Ok, a primeira parte desta dica não vale para o PHP, mas muitos programadores de outras linguagens não fazem ideia de que existe mais de um tipo de compilação. Geralmente dois: debug e release.

Quando compilamos nossa aplicação como debug (o default), diversas meta-informações são inseridas nela para que o debugging possa ocorrer, para que a pilha de erro dê mais informações sobre cada exceção lançada e muito mais. Nada disso é necessário (ou não deveria) quando sua aplicação vai para produção.

Sendo assim, sempre que existir a possibilidade (e sua linguagem for compilada), procure o jeito correto de publicar sua aplicação em modo release (produção) para que ela tenha o menor tamanho e maior performance básica possível.

Além disso, mesmo que sua aplicação não seja compilada, sempre existem ajustes que podemos fazer para melhorar sua performance em produção, geralmente ajustes associados às variáveis de inicialização, de ambiente e arquivos de configuração. Não sou nenhum expert em PHP, mas tenho certeza que existem configurações que você pode fazer.

Mesmo as linguagens compiladas possuem tais configurações, como no caso do web.config do ASP.NET por exemplo, que possui diversos ajustes de performance como remover autenticação quando não é utilizada e o mesmo para sessão, só para citar dois exemplos. Estude os arquivos de configuração da sua linguagem web e verá que há muito mais neles do que apenas a string de conexão com o banco. 😉

Dica 4: Tipos de variáveis

Você deve achar que estou maluco, afinal, o que tipos de variáveis têm a ver com performance do meu site? Tudo!

Talvez não se lembre das primeiras aulas de algoritmos (supondo que você fez/faz um curso técnico ou faculdade de computação), mas uma das primeiras coisas que aprendemos são os tipos de variáveis e o quanto eles ocupam de espaço na memória do computador. Quanto maior a capacidade e os poderes do tipo que você escolher, mais memória ele vai consumir e muitas vezes mais processamento também.

Um alerta aos programadores PHP e outras linguagens com tipagem dinâmica: vocês também devem se preocupar com os tipos que vocês inferem dinamicamente às variáveis!

Mas, dando um exemplo prático, um INTEGER (int, Int32, inteiro, como preferir) geralmente ocupa 4 bytes de memória, ou 32-bit. Isso dá a ele (aproximadamente) 4.3 bilhões de números possíveis ou quase a idade do planeta Terra. Falando em idade, você já notou como é comum os programadores usarem INTEGER para variáveis de idade e semelhantes? Se a sua linguagem possui outros tipos de dados numéricos, como o SHORT INTEGER (short, Int16, etc), você deve usá-lo para evitar desperdícios como esse do exemplo, de guardar a idade de uma pessoa (que dificilmente passa de 100 anos) em uma variável que comporta até 4.5 bilhões de anos.

O mesmo vale para tipos com ponto flutuante. Com exceção do PHP, onde double e float são a mesma coisa, geralmente eles não são. Algumas linguagens, inclusive, possuem um terceiro tipo chamado decimal. O float é o equivalente do integer quando o assunto é ponto flutuante, sendo que ele usa parte dos bits para o número inteiro e parte para os decimais. Já o double possui esse nome pois é um float de dupla precisão, ou 64-bit, com muitos mais possibilidades ‘antes e depois da vírgula’. Já o decimal têm foco nas casas decimais, com uma precisão absurdamente maior que o float, mas com uma grandeza menor de números inteiros disponíveis. Ou seja, dependendo da linguagem que você usa, existem diversas opções de variáveis para ponto flutuante, uma para cada ocasião e depois de ler isso deve ter ficado óbvio que o double é o mais custoso deles para a máquina em termos de recursos consumidos.

Claro que isso somente faz diferença em grandes quantidades, em sites/sistemas com muitos acessos. Mas considerando que todos queremos que nosso projeto seja bem sucedido um dia, que tal perder mais alguns minutinhos agora já programando do jeito certo?

Outro exemplo, ainda mais dramático são as variáveis de texto. String, text, CHAR*, STR, não importa. Variáveis de texto geralmente estão associadas a alto consumo de memória, principalmente devido às características intrínsecas de tais dados, como sua imutabilidade (exceto no PHP), suporte à múltiplos caracteres (o que exige muitos bits para cada caracter) e por aí vai. Em algumas linguagens como o C#, cada String consome 20 bytes de memória só por existir, e mais 2 bytes para cada caracter que ela tiver, é muita memória! É quando entra nossa segunda dica.

Dica 5: Não fique manipulando Strings diretamente

Isso é especialmente importante para quem trabalha com linguagens fortemente tipadas como Java e C#, mas também pode ser levada em consideração por quem usa PHP.

Qual a diferença? Em linguagens como Java e C#, as Strings são imutáveis, elas não podem ser alteradas. Quando, em nosso código, acreditamos estar alterando uma String, na verdade, estamos criando uma nova sem saber, o que obviamente nos leva a um problema enorme de memória se ficarmos manipulando Strings muito grandes ou mesmo Strings pequenas – se fizermos isso muitas vezes, em um laço, por exemplo.

Nestas linguagens, o ideal é usar buffers de Strings, como o StringBuilder do C# e do Java, que manipula Strings de maneira mais inteligente, sem ter de ficar recriando-as o tempo todo.

E no PHP?

No PHP as Strings são mutáveis, então não há uma perda tão grande em uma manipulação extensa de strings, embora a performance de suas Strings não sejam tão boas quanto um StringBuilder. Mas é um ótimo meio-termo!

Ainda falando de Strings, vamos à próxima dica!

Dica 6: Use expressões regulares quando necessário

Se você não conhece expressões regulares, deveria!

Acontece que quando temos tarefas de processamento intenso sobre textos, envolvendo condições complexas, múltiplas condições simples ou tarefas textuais que só de olhar o código digitado você já percebe que tem alguma coisa errada, é hora de usar expressões regulares.

Por exemplo, você quer verificar se um padrão de palavra existe dentro de um texto (ou não existe). Você pode fazê-lo testando todas as possibilidades com um ‘contains’ tradicional, ou simplesmente resumir a um único IF em cima de uma expressão regular com o padrão de palavra que está buscando. O mesmo vale para ‘REPLACES’ condicionais, extração de texto, validação de formatos, etc. Se testes complexos ou mesmo muitos testes simples devem ser feitos em strings, é hora de pensar em usar uma REGEX.

Claro que existem muitas dicas de performance que poderiam ser dadas somente acerca do uso de regex em seu código, mas em linhas gerais, é uma dica a ser utilizada sempre que necessário. Não seja preguiçoso e faça certo!

Dica 7: Cuidado com o que você declara

Isso é uma verdade até mesmo na vida real 😀

A quantidade de variáveis que declaramos, e principalmente, os objetos que declaramos (caso esteja usando orientação a objetos) impactam profundamente o desempenho da nossa aplicação, principalmente quando associados à laços de repetição ou chamadas recursivas.

Como assim?

Uma variável declarada ou objeto instanciado fora de um laço, em uma situação normal, é criado uma vez em memória. A mesma variável ou objeto declarado dentro de um FOR ou WHILE é criada uma vez em memória…para cada iteração do laço!!!

Junte isso ao que vimos antes sobre tipos de variáveis e principalmente Strings e veremos que declarar variáveis e objetos dentro de laços é algo que somente deve ser usado em último caso, pois geralmente 90% do custo computacional de um sistema está nos 10% de trechos de código que programamos, geralmente onde laços estão envolvidos.

Então, “bora” declarar suas variáveis fora dos laços de repetição?

O mesmo vale para chamadas recursivas, que facilmente podem estourar a pilha de memória da sua linguagem se ficarem criando objetos e variáveis a torto e a direito.

Dica 8: Não sobrecarregue suas requisições

No post sobre front-end, falei muito sobre diminuir o número de requisições, aqui o foco é diminuir o ‘custo’ de cada requisição: consumo de CPU, memória, etc.

Cada vez que o usuário interage com sua aplicação web, uma requisição é feita ao back-end, certo?

Se a cada requisição você tiver de fazer um monte de verificações, carregamentos, validações, conexões ao banco e por aí vai, não é preciso ser um gênio do MIT para ver que não vai dar certo, que tudo vai ficar muito lento rapidamente.

A regra é clara: se você faz sempre as mesmas tarefas durante as requisições ao servidor (como verificar no banco se o usuário tem permissão para acessar uma página) é hora de repensar sua estratégia pois há um jeito melhor de fazê-la.

Claro que eu não tenho como cobrir todas aqui, pois cada linguagem tem as suas particularidades. No NodeJS, por exemplo, uma prática comum, e errônea, é carregar a conexão com o banco de dados junto às requisições, para não onerá-la. No ASP.NET, fala-se de não usar ViewState para não ficar carregando um monte de informação, geralmente inútil, junto aos postbacks. Mas em linhas gerais, o que estou sugerindo é que pesquise as boas práticas do seu framework/linguagem de programação quando o assunto é ‘idas e vindas do servidor’.

Uma dica genérica e excelente para isso é a seguinte.

Dica 9: Use cache

Se o seu backend é muito requisitado para efetuar operações ou consultar dados muitas vezes repetidores, pode ser uma boa oportunidade de usar algum tipo de caching.

Vale tudo aqui: cache in-proc, plugins, Redis, memcached, não importa. A ideia é, se você vai demais no servidor para pegar sempre os mesmos dados, um cache vai te ajudar, e muito.

Boa parte dos sistemas, principalmente aqueles em que o usuário se autentica para usar, acabam consultando muita informação repetida durante a navegação do usuário por dentro da ferramenta e, muitas vezes, estes dados não se alteram com frequência para terem de ser consultados “à quente“ toda hora.

Por exemplo, as próprias informações da conta do usuário logado. No momento que ele se autenticar, você deveria guardar as principais informações dele que você pode precisar ao longo da sessão de navegação, como o nome e foto, por exemplo, mas também quais produtos/serviços ele tem contratados com você. Essas são informações que geralmente são exibidas em todas telas, e você não vai querer ir no banco a cada requisição de troca de tela para pegar a mesma coisa, certo?

Estas foram mais algumas dicas que acreditamos que possam fazer uma diferença significativa no desempenho do seu site e certamente existem muitas outras que poderíamos aplicar, principalmente se entrarmos no detalhe de linguagens específicas. No próximo post, vamos abordar melhorias no banco de dados, a começar pela escolha do banco certo para sua aplicação (sim, você pode estar usando o banco errado neste exato momento!).

E você, tem alguma dica de performance para back-end? Nos conte aqui nos comentários 😉

  • Maddy Ezz

    boa