12 Lições aprendidas em Testes de Performance Server Side

Tenho trabalhado com performance nos últimos meses e tive a oportunidade de parear com algumas pessoas muito experientes no assunto, e juntos até desenvolvemos um conjunto de rake tasks para execução automática de testes, o rake-jmeter, que falarei em um futuro post.

Como QA do projeto, eu fiquei encarregado de desenvolver os testes, coletar as informações de aceite, diagnosticar os principais problemas e dependências, gerar relatórios periódicos, acompanhar as melhoras e integrar os testes de performance ao build pipeline. Para isso escolhemos o JMeter, ferramenta open source, simples de usar, conhecida e muito testada na comunidade. Escolhemos ela principalmente por ter uma interface de linhas de comando que ajuda na distribuição dos testes em vários servidores ao mesmo tempo e por ser uma ferramenta com muita credibilidade.

Além do JMeter, outro elemento fundamental para o sucesso dos testes foi o NewRelic, ferramenta de monitoramento de performance em tempo real, que facilitou a visão do comportamento interno e identificação de gargalos e erros, enquanto o JMeter se encarregava de estressar a aplicação e catalogar o comportamento externo.

Abaixo algumas observações e lições aprendias ao longo desses últimos meses no fantástico mundo dos testes de performance:

1 – O tempo médio é só uma promessa

É muito comum ver critérios de aceite de performance baseados exclusivamente em tempo médio de resposta. Já presenciei muitos profissionais em listas de discussão e até materiais de referência para certificações, treinamentos ou ferramentas, citarem que tempos mágicos como quatro ou seis segundos de tempo médio seria o requisito de performance, mas aqui existe uma grande cilada.

O tempo médio de resposta é a soma de todos os tempos de resposta divididos pelo número de transações avaliadas. Por esse motivo ele é facilmente confundido com um número que define o quanto o visitante da página vai esperar quando acessar a página sob a carga experimentada durante os testes. O tempo de médio de resposta deve ser visto sim, mas como um dos fatores de aceite para o desempenho de uma aplicação web, mas nunca como o fator determinante para que essa aplicação entre em produção.

Para exemplificar como o tempo de resposta pode ser usado da forma errada, vamos supor que após executar testes em uma página web, essa página tenha nos retornado nove requests com os seguintes tempos de resposta:

5, 11, 5, 1, 5, 2, 1, 5 e 1.

Pode ser representado pelo seguinte gráfico:

Exemplo de requests com diferentes tempos de resposta ao longo do tempo

 

Nosso product owner disse que os usuários desse tipo de sistema, normalmente abandonam o site ou perdem o foco quando estão acessando a aplicação e ela demora cinco segundos ou mais para responder. Nesse caso, nosso objetivo é que os usuários não aguardem mais do que quatro segundos e meio para ver a página completamente carregada.

Se olharmos o tempo médio das requisições acima vamos ter (5+11+5+5+5+2+1+1+1)/9 = 4 segundos. Podemos então, baseado apenas no tempo médio de resposta, dizer que os nossos testes estimam que os usuários estarão felizes usando essa aplicação, pois o tempo médio é menor que o ponto em que os usuários se frustram. Mas se observarmos de outro ângulo, podemos notar que nos nossos nove registros, apenas quatro estão abaixo do tempo de resposta desejado pelo product owner, logo, por essa outra forma de ver o mesmo requisito, podemos dizer que apenas 44% dos nossos usuários estariam verdadeiramente satisfeitos baseando-se nesse tempo de resposta de quatro segundos e meio.

O tempo médio é um indicador importante, pois ele agrega os tempos de resposta e facilita a identificação de páginas ou APIs mais demoradas, mas ele não é e nunca deve ser visto como o critério definitivo de aceite, pois ele é um número muito maleável e pode facilmente enganar o analista de performance. O ideal é observar inicialmente a variação no tempo de repostas que é representada pelo desvio padrão e a porcentagem de transações abaixo do nosso tempo máximo (leia mais logo a frente).

2 – Avalie o Tempo de Resposta Abaixo Limite como Critério de Aceite

Uma das maneiras mais interessantes de medir a satisfação dos usuários é baseado-se do tempo de resposta das transações, e não em números derivados dele. Ao contrário do tempo médio, devemos buscar os valores e criar um percentil, ou seja, verificando quantos porcento das nossas transações estão abaixo de um determinado tempo de resposta.

Imagine que o seu Product Owner determina que um usuário está satisfeito quando o tempo de resposta é inferior a cinco segundos. O tempo médio como mostrado anteriormente, pode ser usado, mas ele não é a prova de falha. Uma alternativa inteligente é determinar uma quantidade de usuários que devem ser atendidos por determinados tempos de resposta, por exemplo, que 95% dos requests devem responder no tempo máximo estipulado pelo product owner. Nesse caso, temos um objetivo muito claro, só temos que monitorar as transações e estabelecer o percentil de 95% para isso.

Gráfico de Percentil para o mesmo conjunto de dados usado no exemplo do tempo médio

O gráfico acima demonstra o tempo de resposta na coluna Y e a porcentagem das transações abaixo daquele tempo de resposta na coluna X. Essa visão facilita o entendimento do que falamos anteriormente, pois mostra que apenas 44% das transações tem um tempo de resposta abaixo dos cinco segundos, ou seja, os outros 56% das transações tem um tempo de resposta acima do tempo estipulado pelo PO.

O gráfico acima é apenas um exemplo, mas em um projeto real, podemos estabelecer tempos de resposta esperados para dois ou mais pontos, por exemplo estabelecendo que 60% das transações tem que ter um tempo de resposta abaixo de um segundo e 95% das transações devem ter um tempo de resposta abaixo de quatro segundos.

3 – Olhe o seu sistema como um encanamento

Gargalos de performance normalmente ocultam outros gargalos posteriores a ele. Em sistemas complexos temos dezenas de micro-serviços que fornecem entradas para outros micro-serviços é muito comum que um desses micro-serviços se torne o gargalo, e oculte o real comportamento dos micro-serviços posteriores.

Quando um micro-serviço demora para responder ele reduz a quantidade de transações de todos os próximos serviços, afetando o tempo de resposta de todos os próximos micro-serviços de forma “positiva” (diminui o tempo de resposta), pois o número de requests que as apis posteriores vão receber fica muito abaixo do que deveria em uma situação em que o micro-serviço anterior respondesse de maneira mais performática (menos carga).

Para exemplificar, vamos supor que o The Bug Bang Theory é uma aplicação web extremamente complexa com dezenas de micro-serviços descritos na figura abaixo:

Exemplo de página construída dinamicamente por fragmentos baseados em chamadas a APIs.

Vamos supor que ao executarmos um teste de carga na home page, identificamos um tempo de resposta muito superior ao que deveríamos ter, então abandonamos o JMeter e iniciamos uma pesquisa para saber como o tempo da home page é distribuído entre os serviços que a compõe. O resultado da nossa pesquisa é:

Distribuição do tempo de resposta das APIs que compõem a Home Page do nosso exemplo

 

No exemplo acima, identificamos que 85% do tempo de resposta da nossa home page hoje, é resultado de um desempenho muito baixo da nossa API de texto. Nesse momento podemos afirmar que a API de texto não está performando dentro do necessário, mas não podemos afirmar que nenhuma das outras apis está respondendo de maneira satisfatória. Usei a analogia do encanador, pois assim como a agua percorre os canos, os requests percorrem as APIs. Caso uma API esteja “estreita” demais (pouca vazão), isso compromete a análise de todas as APIs posteriores. Em outras palavras, quando uma API demora muito para responder, o número de requests que vão para as demais é reduzido, pois o sistema ainda está aguardando a resposta da thread anterior. Se esse número for muito baixo, ele e pode não ser o suficiente para destacar um problema de performance.

Nesse momento deve ser feito um teste nesta API para tentar diagnosticar qual o problema com ela (veremos mais a frente um pouco mais sobre isso) e assim que determinada a solução a mesma análise deve ser refeita. Quando trocamos “os tubos” por alguns mais largos, que permitem uma vazão maior, podemos ver novos pontos estreitos (resultados abaixo dos esperados) e então devemos verificar novamente o problema e continuar o ciclo.

Para realizar esse tipo de análise podemos utilizar por exemplo o NewRelic. Uma saída mais hardcore seria criar logs para cada ponto de integração do sistema e avaliar o comportamento através desses logs (logo a frente).

4 – Dividir para conquistar

Muitas vezes você não vai poder olhar a performance como um todo, mas sim como pequenos focos de incêndio. Quando falamos dividir, não estou falando em deixar de ver todo o cenário de compra ou de cadastro de cliente, e focar ná página de produtos ou especialmente no principal request dessa página mais caótica, mas sim em ir muito mais fundo.

Como visto no exemplo anterior alguns requests podem fazer sub-requests, e muitas vezes apenas um desses sub-requests pode ser o problema para uma página ou para um sistema inteiro. Outras vezes pode ser um algoritmo com um custo muito elevado, que processa dois ou três repetições alinhadas. Seja como for, na maioria das vezes esses problemas não se manifestam quando executamos com um usuário, pois em termos de processamento esse algoritmo ou esse sub-request é muito simples, mas quando repetido dezenas ou centenas de vezes por segundo o custo dessas operações aparece.

Para identificar esse tipo de problema, existem algumas soluções, entre elas:

1 – Auxilio de ferramenta própria para trace de código

Algumas ferramentas como o New Relic citado anteriormente tem a capacidade de avaliar o comportamento de pequenos fragmentos de código, e mapear quais são os trechos mais lentos e até indicar os possíveis culpados por isso, como por exemplo um banco de dados ou uma API externa.

Exemplo de requisição detalhada no New Relic

 

A imagem acima retirada desta página do NetTutsPlus mostra um request sendo dividido em sete selects muito custosos ao banco de dados que juntos são 84% do tempo de resposta desse request.

2 – Logar os tempos de reposta para cada chamada/algoritmo manualmente

Nem sempre temos uma ferramenta na nossa linguagem ou no custo do projeto, mas não é por isso que deixamos de avaliar o custo em tempo de resposta, de sub-requests ou algoritmos. Existe uma maneira muito simples e disponível na maioria das linguagens que consistem em buscar o tempo antes e depois do elemento em foco e ver qual é a diferença entre os dois.

startTime = Time.now
myRequest # or myAlgorithm
stopTime = Time.now
puts stopTime - startTime

Dessa forma podemos ver qual é o tempo daquele pedaço especifico de código, seja ele um algoritmo, um sub-request, consulta a um banco de dados ou qualquer outra coisa. Claro que essa técnica pode (e deve) ser melhorada para legibilidade, como por exemplo exportando um arquivo html com os tempos médios e máximos de cada um dos requests (basicamente o que o NewRelic faz).

5 – Automatize o processo de execução, operação e análise

Mudanças de performance não são simples. Em sua maioria envolvem detalhes arquiteturais, forte mudança na lógica e complexidade dos algoritmos, estratégia de cache, mudança de componentes, interface com sistemas externos, atualizações de bibliotecas, mudança de hardware entre outras abordagens que podem ajudar a melhorar o tempo de resposta das páginas. Raramente podemos nos dar ao luxo de adicionar mais duas dezenas ou centenas de máquinas virtuais na nossa nuvem, e nesse contexto cada detalhe modificado pode surtir um feito diferente no sistema.

Não podemos demorar um dia para ter uma nova “foto” da nossa performance e muito menos para conseguirmos analisar. Temos que ter isso em minutos! Além disso, correções de performance podem (e vão na maioria das vezes) introduzir defeitos funcionais, principalmente quando essas correções ou melhorias envolvem de alguma forma caching (falaremos mais a frente sobre isso). Nesse contexto, testes automatizados de UI (sim, selenium-webdriver like) são ótimos aliados (ver mais a frente) e uma forma de executar, coletar e criar um diagnostico inicial automatizado são muito bem vindas também.

No meu caso, para cada teste de performance eu tinha que:

  1. Escrever ou modificar o teste
  2. Enviar os arquivos via SCP para quatro servidores
  3. Ir em cada um desses servidores (SSH) e iniciar o JMeter-Server com as configurações corretas
  4. Ir até o servidor principal e iniciar os JMeters-Servers com o arquivo correto
  5. Aguardar o termino da execução
  6. Baixar o arquivo .jtl para a minha máquina local
  7. Abrir o JMeter local para ver os números e gráficos
  8. Tirar print screen ou exportar cada um dos oito gráficos que eu precisava avaliar
  9. Copiar dados de duas tabelas que o JMeter gerava
  10. Fazer algumas contas para conseguir alguns números
  11. Concluir quais itens estão abaixo dos critérios de aceite
  12. Escrever um arquivo MS Word com todas essas informações

Na terceira vez que eu fiz isso no mesmo dia eu pensei em me matar :( ou automatizar todo esse processo :). Nesse ponto eu e o Carlos Villela nos juntamos e escrevemos um conjunto de rake tasks para fazer tudo isso e algumas coisas a mais chamado rake-jmeter. Com isso eu consegui ser muito mais produtivo, e conseguia fazer deploys de novos testes, gerenciar execução e gerar o relatório com todos os gráficos e dados que eu precisava usando um minuto a mais do que o tempo de execução do teste de carga/stress.

6 – Cache não resolve todos os problemas

O cache é um mecanismo desenvolvido para aliviar o processamento nos servidores. Ele cria uma versão estática (HTML puro) de uma página construída dinamicamente, dessa forma, se o tempo de criação de uma página é de 10 segundos, a primeira execução vai gastar 10 segundos, mas todos os usuários que visitarem essa mesma página até o cache expirar, vão receber a mesma página em um tempo muito baixo, muitas vezes beirando os 100 mili-segundos. Para saber mais sobre sistemas de cache leia https://www.varnish-cache.org/

Muita gente acredita que um sistema de cacheamento pode resolver todos os problemas de performance, especialmente quando temos dezenas de milhares de visitas em períodos de tempo muito curtos como uma ou duas horas. Sendo bem honesto, se não fosse o cache, sistemas como Facebook, Google, Twitter entre outros gigantes do dia a dia de milhões de pessoas não suportariam a carga que eles suportam. Mas aproveitando o gancho, apesar de uma estratégia de caching muito bem planejada, nenhum desses sistemas sobrevive unicamente por causa do cache, mas sim por dezenas ou centenas de técnicas, onde algumas delas são relacionadas de alguma forma com caching.

Exemplo de uma aplicação com cache expirando a cada minuto

O gráfico acima mostra um exemplo de um sistema com baixa performance e com cacheamento, onde o cache para cada página expira a cada minuto. A cada sessenta segundos, pelo menos um request para cada página vai até os servidores e atualiza o conteúdo do cache que fica na frente desses servidores, que a partir de então, respondem aos novos requests com esse texto html que existe em memória no servidor de cache, onde o custo para entregar o texto é muitas vezes dezenas ou centenas de vezes mais rápido e barato do que buscar novamente no servidor.

O que acontece quando temos um sistema totalmente dependente de cache como no exemplo acima, é que nos momentos em que o cache expira, o tempo de resposta aumenta exponencialmente de acordo com a carga atual. Imagine um sistema com cache, onde os usuários estão acostumados com respostas tão rápidas quanto um piscar de olhos, e de repente o sistema fica lento (cache expira). O que o usuário faz? Recarrega a página para ser “mais rápido”. Agora imagine que nesse momento, uma quantidade gigantesca de usuários fez a mesma coisa a cada dois segundos.

Isso significa uma quantidade absurda de requisições que não pararam no servidor de caching, passaram para a aplicação que não performa direito, e todos ao mesmo tempo estão pressionando os servidores por resposta, e de dois em dois segundos, estão enviando uma nova requisição para o servidor (recarregando a página). Facilmente um servidor pode entrar em swap e até mesmo deixar de responder, comprometendo a disponibilidade de serviços, conteúdo, publicidade e qualquer outra coisa vinculada aquela aplicação web.

Por isso, mesmo que tenhamos um Varnish ou qualquer ferramenta mágica que cria cache e transforma seu tempo de resposta de qualquer número para 200 ou 300 milisegundos para todos os seus 50.000 usuários, lembre-se que o cache vai expirar em algum momento, e quando isso acontecer, a sua aplicação tem que responder a esses primeiros requests o mais rápido possível. Ainda por que, alguns requests como requests sob segurança ssl (https) e requests de modificação como POST, PUT e DELETE não podem ser cacheados.

7 – Testes automatizados de UI são os seus melhores amigos

Sim. Se você usa cache nenhum teste vai te resguardar das funcionalidades a não ser os testes que navegam na aplicação no ambiente de staging, que assim como produção, deve ter a mesma estratégia de caching. Mas porque precisamos desse tipo de coisa?

O cache cria uma cópia da página ou de parte dela como vimos antes, mas vamos imaginar que por algum motivo, durante uma melhoria de caching do Facebook, uma rota de home de usuário foi cacheada de forma errada acidentalmente e vamos supor, que eles optaram por usar o mínimo de testes funcionais automatizados possível, só um com um usuário passando por algumas páginas, afinal, esses testes são lentos, custosos e “sempre” podem ser substituídos por testes de integração, unidade ou mesmo de interface, ou podemos usar algum mecanismo que melhore o desempenho como o VCR.

O desenvolvedor commita e pusha as melhorias de performance, o pipeline de testes de unidade é iniciado, todos os testes de unidade passam, os testes de integração passam, os testes de UI em ambiente de QA que usam algumas estratégias de melhoria de desempenho para execução de testes funcionais automatizados também passam, finalmente os testes de performance são acionados e os medidores apontam uma melhoria substancial na página de usuário. Enfim, todos os testes de unidade, segurança, integração, interface e performance passaram e o sistema é promovido automaticamente para produção.

O usuário João da Silva acessa a sua página do Facebook e aciona o mecanismo de caching. Em instantes, todas as pessoas que atualizam ou vão para a home page do facebook, instantaneamente veem a página do João da Silva e todas as informações pessoais, inclusive as informações privadas.

Escopo de cada nível de teste, incluindo um servidor externo exclusivo para cacheamento com Varnish

Para evitar esse tipo de problema não pode ser automatizado de uma maneira simples nos leveis inferiores, pois o servidor de cache vem como uma camada extra da aplicação, e como as camadas inferiores não devem conhecer nada das camadas superiores, nenhum teste de unidade, integração ou aceite vai falhar se alguma configuração de cache estiver errada.

Uma abordagem intuitiva de cobrir cache usando testes automatizados, é usar testes pós-deploy em stage ou outro ambiente semelhante (preferencialmente um anterior a stage). O requisito para que esse teste seja válido é que exista uma configuração de cache e que esse teste seja executado acima dessa camada, cacheando a aplicação e usando quantos usuários forem necessários para validar as características específicas de cache que a aplicação requer. No mais é um teste funcional comum, navegando pela aplicação e realizando asserções (verificando se algo está na página) e preferencialmente, nesses testes contra asserções (verificar se algo não está na página) contra os elementos de cache também.

Uma segunda alternativa é que o próprio teste de performance valide esse tipo de informação, usando por exemplo, assertions do jmeter. O problema aqui, é que isso aumenta a complexidade e o processamento dos testes de performance, exigindo que o ambiente de execução dos testes (onde o jmeter está rodando) seja mais poderoso. Ainda assim, acho interessante ter alguns testes especiais para validar as regras do caching.

8 – Seu sistema performa como um atleta

Não existe formula mágica para um número de threads. Para alcançar o throughput e o tempo médio desejado são necessários experimentos e experimentos. Como um atleta mede seus limites aos poucos, para entender até aonde ele pode chegar, devemos também medir a carga que o nosso sistema suporta, subindo-a incrementalmente e estudando o comportamento do sistema sob cada carga que executamos.

Não estou falando de testes de stress, onde escalamos a carga em um período de tempo muitas vezes curto para saber onde o sistema começa a falhar, mas sim de aumentar a carga gradativamente até o ponto em que o sistema performa melhor. Para isso podem ser vistos alguns indicadores, mas o meu preferido é o throughput.

O throughput ou vazão é a quantidade de requests que o sistema consegue atender em um segundo (ou minutos em alguns casos). Esse número está intimamente atrelado ao tempo médio de resposta. Quanto mais rápido o meu sistema responder, maior será o meu throughput.

Exemplo meramente ilustrativo da relação percebida entre o throughput, o tempo médio de resposta e desvio padrão de acordo com aumento do numero de threads

Quando olhamos o desempenho do software com base na vazão sob uma determinada carga, estamos avaliando qual a carga máxima que ele suporta antes de entregar menos. Existe um número em algum ponto, que quanto aumentarmos a carga o sistema vai começar a responder menos requisições do que com a carga inferior que estávamos executando, neste momento podemos observar também o aumento do tempo médio de resposta de resposta. Daqui em diante o tempo de resposta aumenta, o desvio padrão aumenta e o throughput diminui. Quanto mais aumentarmos a carga a partir de agora, mais essa tendência vai aumentar, até um momento em que o servidor não vai mais conseguir responder as requisições.

Escale sua carga até encontrar esse ponto e saberá qual é a carga máxima de resposta do servidor. Verifique o tempo médio de resposta para saber e diminua a carga até o tempo que o médio atenda seus critérios de aceite e finalmente verifique o desvio padrão ou o percentual de transações abaixo do tempo máximo de resposta aceitável. Feito isso, vai saber qual é o número de transações por segundo máximo ideal para a sua aplicação. Para aumentar esse número você pode fazer experimentos melhorando o hardware, aumentando o número de nodos, mudando configurações de cache ou mudando a arquitetura da sua aplicação. Mantenha uma baseline dos seus testes e aos poucos modifique o número de threads até alcançar o esperado em produção.

9 – Conheça sua população, e reproduza seu comportamento e seus hábitos

Os testes de performance não devem avaliar simplesmente se o sistema Y, com o volume X suporta a carga Z, mas sim que o sistema Y, sob inúmeras condições que envolvem o volume X, suporta a carga Y distribuída de forma orgânica e semelhante ao esperado ou conhecido de produção, ou seja, nada de testes de performance viciados.

Testes viciados tendem a afetar de maneira significativa alguns dos critérios que usamos para avaliar a performance de uma aplicação, principalmente o tempo de resposta (para mais e para menos) e eu tive o infeliz prazer de encontra-los nesses últimos dois projetos…

Em um caso eu estava executando um teste e os resultados de performance eram muito ruins em várias páginas. Verificando o comportamento no New Relic observamos que por algum motivo a api de credenciais era aparentemente o principal problema.

Quando mudamos os usuários descobrimos que o usuário que estávamos usando possuía todas as credenciais para todos os produtos. Por isso ele carregava um json* com todas essas autorizações, o que não impacta na performance quando o número de acessos desse tipo de usuários é menor que 0.002%, como é em produção. Criamos centenas de usuários com o perfil mais próximo aos usuários que acessam o sistema no dia a dia e executamos novos testes.

*O problema de performance de super usuários foi reportado mas permaneceu como baixa prioridade por algum tempo, já que eram poucos usuários internos acessando a app com a conta corporativa.

Em um segundo caso, o simples fato de uma página não possuir todos os produtos disponíveis fez com que um problema de performance fosse identificado um pouco mais tarde. A página suportava 50 produtos e apenas 10 estavam disponíveis na massa de dados do teste. Uma diferença aparentemente inofensivo, afinal, vamos submeter o cenário a uma carga com um percentual de segurança bem alto, que deve cobrir qualquer diferença de dados de exibição.

O problema é que a arquitetura da aplicação executava 10 consultas externas para cada um dos itens disponíveis na página, ou seja, para cada novo item disponível, adicionávamos 11 vezes o número de threads em novos requests para essas apis. Se fizermos a conta usando 100 usuários com dez produtos, teremos 100 threads * 10 produtos * 11 requests, que nos dá 11.000 requests. Se dobrarmos o número de threads por exemplo (margem de segurança, vamos ter 22.000 requests. Mas se adicionarmos 40 produtos ao invés de 10 novas threads, passamos a ter 44.000 requests  (100 threads * 40 produtos * 11 requests ).

Claro que esse número exponencial foi trabalhado e reduzido bastante, mas foi um detalhe que precisou ser observado com bastante atenção, caso contrário, poderia ter sido um grande problema em produção, quando os quarenta ou cinquenta produtos estivessem efetivamente lá e mesmo com um número muito inferior de usuários o tempo de resposta seria possívelmente superior ao apresentado nos testes de performance.

10 – Seu ambiente é exclusivo e o mais próximo de produção possível

Quanto mais próximo de produção melhor, mas existem alguns detalhes importantes para serem lembrados:

Ambiente não é só infraestrutura

Não basta copiar a infraestrutura, todas as configurações, os serviços externos, a massa de dados, o código fonte e todos os outros artefatos que fazem parte do ambiente de produção devem ser replicados.

Ficar atento aos custos

Copiar a infraestrutura inteira de uma aplicação de pequeno porte, que ficam hospedadas em um servidor, é inteiramente possível, mas imagina como seria replicar um sistema como o Facebook, que tem uma infraestrutura quase incalculável. Em termos de infráestrutura, o custo de manter um ambiente como esse para ter a estimativa de performance da execução dos geradores e monitores de carga não compensa. Nesses casos mantemos a mesma arquitetura e infraestrutura em uma proporção menor, porem escalável e estabelecemos um quociente P de performance. Com isso, estabelecemos um baseline e vigiamos a variação da performance dessa aplicação de acordo com as mudanças.

Apis ou serviços externos

Sua api tem um serviço externo, mas o proxy para testes desse serviço, apesar de funcionar muito bem para seus testes de aceite, não funcionam para os testes de performance. Por exemplo um provedor de serviços de cartão de crédito que tenha um canal para realização de testes funcionais, mas que esse mesmo canal não resiste a pequenas variações de número de requests. Ao mesmo tempo seria muito custoso testar com dados reais (haja limite no cartão de crédito). O que fazer? Não teste seus serviços externos, mas teste sua aplicação.

Mesmo que você encontre problemas nos serviços externos, você não será capaz de resolvê-los, afinal, você não tem nenhum controle sobre isso… por isso Mock esses serviços e adicione um tempo de resposta que varia randomicamente entre o mínimo e o máximo para cada um deles. Caso tenha dados mais precisos e consiga estabelecer um padrão desse tempo de resposta, use-o. Claro que antes de adquirir essas apis você vai fazer os testes necessários e vai verificar as garantias que ela fornece, além disso sua aplicação não pode falhar nem ficar lenta se o seus serviços externos falharem e esse é um ótimo teste que quase ninguém lembra de fazer quando o serviço passa nos testes iniciais :)

Exemplo de monitoramento real time para servidores web

 

Monitore os recursos utilizados

A imagem anterior mostra como diferentes recursos em duas máquinas, representadas pelas linhas laranjada e azul, se comportam de acordo com o crescimento e estabilidade da carga. Esse tipo de acompanhamento é fundamental para entender quando o hardware é um gargalo. Uma ferramenta real time pode ajudar no monitoramento e torna-lo mis gráfico e inteligível, mas qualquer pessoa com conhecimento no sistema operacional em questão pode trabalhar para coletar esses dados.

O ambiente que hospeda o teste também precisa de “teste de performance”

O ambiente em que o gerador de carga está hospedado também precisa de atenção e de testes. Teste ele contra uma página stática e descubra se existe alguma interferência. Em um dos nossos testes, descobrimos que existiam algumas limitações de threads e sockets em algumas das máquinas da nossa nuvem, o que foi facilmente solucionado, mas que se não fosse percebido poderia ter causado muitos ruídos e problemas nos dados, como por exemplo, um número menor de throughput.

Para facilitar a identificação, pode-se criar uma página stática bem leve no servidor, e acionar os geradores de carga, Durante a execução, avaliar se cada um dos nodos da nuvem tem quantidades equivalentes de consumo dos diferentes recursos, tais como banda, memória e cpu. Além diss, avaliar se a quantidade de requests é baixa ou se é aproximada a uma função mais complexa, como uma página que gera um token aleatório, ou mesmo diminuir o número de nodos e ver se os resultados são parecidos. No mais, procure ter uma pessoa de infraestrutura com você para esses testes, normalmente eles sabem bem como monitorar se um servidor de carga tem limitações.

11 – Tenha uma ferramenta de monitoramento real time

Eu não estou fazendo propaganda para o New Relic, mas ele foi crucial para um feedback e diagnostico rápido nesses últimos projetos. Teste de performance vai muito além de gerar uma carga e receber o número do tempo de resposta (já vimos aqui que não é assim que funciona). Assim como qualquer defeito de software, quando encontrado ele deve ser diagnosticado e muitas vezes corrigido, e essas atividades são muito complexas, especialmente se você não tem uma dessas ferramentas.

Essas ferramentas tem features como identificar as transações mais lentas, verificar as chamadas e tempos de resposta de trechos de código como chamadas de api ou banco de dados, throughput e tempos de resposta da aplicação, dados dos servidores como uso da CPU, rede e memória, tracer de erros que acontecem durante os testes, entre várias outras informações coletadas e organizadas de forma automatizada.

O vídeo acima apresenta algumas features do New Relic e mostra como ele monitora constantemente a aplicação em produção, o que inclui todos os monitores citados anteriormente. Esse tipo de suporte nos que permite saber por exemplo, quando a aplicação responde mais devagar e tomar decisões como quando aumentar a capacidade de processamento da nossa nuvem.

12 – Problemas de performance são doenças degenerativas

Quase nenhum problema de performance nasce grande. Na verdade, quase sempre são pequenos trechos de código, chamadas de apis, iteração com algum serviço interno ou externo, pequeno problema no banco de dados, etc. Em boa parte dos casos citados anteriormente esses problemas são extremamente controláveis e simples de resolver… o problema é que uma abordagem no final do projeto, vai revelar dezenas desses problemas e em muitos casos, em escalas muito maiores.

Se o seu projeto tem requisitos de performance, esses requisitos devem ser mensurados desde o início do projeto. Não estou dizendo para executar milhões de threads do JMeter para cada história ou caso de uso, mas que ao final de cada épico ou entregável por exemplo. Uma abordagem mais inteligente poderia ser adicionar os testes de performance ao seu processo de integração continua, o que não é tão complicado, e pode ajudar bastante com execução automatizada. O exemplo abaixo mostra um exemplo de processo para isso, que consiste em executar testes automatizados o suficiente para garantir que a nova versão do sistema está apto a entrar em produção.

Exemplo de um build pipeline que executa testes de unidade, integração, de aceite, de cache, performance e outros, antes de realizar um deploy automático em produção.

 

Nesse ultimo projeto nós integramos o nosso relatório automatizado dos testes de performance ao nosso build no Jenkins, executando por um curto período de tempo. Usando o rake-jmeter nos distribuímos os testes em quatro servidores de carga nas nuvens e avaliamos o comportamento da aplicação, basicamente usando tempo médio, diversos thresholds, desvio padrão, taxa de erro, throughput/s, mediana e tempo máximo de resposta, além de todos os gráficos e tabelas que o JMeter nos fornece nativamente.

Junto com essa abordagem, estabelecemos critérios de aceite para cada um desses itens que medimos. Dessa forma, além de uma resposta automática do sistema indicando se o ultimo commit está apto para entrar em produção também do ponto de vista de performance, o relatório vem com um guia de issues (esperado vs atual) e todo um conjunto de informações fáceis de entender, onde uma pessoa de negócios consegue tomar decisões rápidas.

Essas foram algumas observações que eu realizei nesses últimos meses e com certeza existem centenas de outros aspectos não cobertos nessas poucas linhas de lições aprendidas. É importante também entender que eu não sou um especialista em testes de performance e esse não é o meu papel full time, embora eu goste muito do assunto e estude sempre que possível. Procure entender o contexto do seu projeto, as definições arquiteturais, converse com os seus desenvolvedores, entenda as necessidades do cliente e pesquise muito para tentar executar o melhor conjunto de testes da maneira mais apropriada para o projeto.

Teste de performance, assim como a maioria das atividades que praticamos no desenvolvimento de software são baseadas em engenharia, matemática e computação, portanto, teste de performance é uma ciência e devemos tratá-la como tal. Simplesmente instalar o JMeter ou qualquer outra ferramenta de load test e buscar um número imaginário sem observar os diversos detalhes relacionados ao processo de planejamento da carga, critérios de aceite, desenvolvimento dos testes, volumetria da massa  de dados, a própria massa de dados, ambiente de teste e o ambiente do teste, execução, melhorias, manutenção dos testes, análise de resultados e diagnostico entre outros.

Espero que tenha ajudado com um pouco da parte teórica. Em breve eu volto com alguma coisa mais prática. :)

Bons testes!

Notas de Rodapé

Camilo Ribeiro

Test Engineer at Klarna
Desenvolvedor, testador e agilista desde 2005, atualmente trabalhando na Suécia.

13 thoughts on “12 Lições aprendidas em Testes de Performance Server Side

  1. Camilo.

    Muito obrigado por compartilhar esta experiência.
    Vejo que a maioria das empresas e profissionais, incluindo eu, não dão a devida importância para a performance, que só tem atenção quando o gargalo é detectado em produção.

    Parabéns.

  2. Isto não é um post, é uma consultoria sobre estratégias para um teste de performance realmente efetivo. Normalmente, o que se lê por aí é sobre alguma ferramenta mágica. Parabéns pela densidade e objetividade!!

  3. Oi Camilo,

    Voce conhece ou indica alguma ferramenta de teste de performance para aplicacoes desktop?

    Att,
    Rafael

  4. Cara ótimo artigo, gostei muito do material e vou começar a pesquisar mais coisas sobre esse NewRelic. Valeu mesmo e ta de parabéns

  5. Muito bem explicado o post, sensasional!!!!
    Somente uma correção, no penúltimo parágrafo do capítulo 2, foi colocado 66% mas o correto seria 56%.
    Espero que continue postando excelentes matérias, está de parabéns!!!

  6. Olá Camilo! Eu estava um pouco afastada da área de testes e não havia visto o seu blog ainda. Estou encantada com seus artigos e como aborda os assuntos de forma clara e concisa! Você está de parabéns! Com certeza vou seguir e recomendar!!!

  7. Olá, Camilo
    Acho que conheço este Product Owner!! Realmente você escreveu muito bem as suas experiências que você tem com teste de desempenho, aliás, tive o prazer de vivencia-las contigo, por isso, valido a profundidade e veracidade destas lições aprendidas que foram muito bem escritas por você.

    Abraços,

  8. Olá Camilo, tudo bem?

    Entrei na área de testes a pouco mais de dois anos e desde então tenho trabalhado com testes de performance.

    Durante esse tempo, pesquisei bastante sobre ferramentas e tipos de testes, porém nunca encontrei um artigo em que esse assunto seja explicado de uma forma tão clara e objetiva.

    Parabéns pelo artigo!!!

Leave a Reply