k6 e Postman — Testes de Carga e Performance

Arthur Faria
10 min readOct 28, 2020

--

Utilizando Postman Collections com K6 para executar testes

  • “Quantos usuários isso aguenta?”
  • “Será que dá conta?”
  • “Qual ambiente preciso para rodar isso?”
  • “Essa nova versão parece mais lenta…”
  • “Essa nova versão está definitivamente mais lenta”

Todas essas perguntas e afirmações são feitas pelo menos uma vez em um ciclo de desenvolvimento de um sistema. Muitas vezes, somos reféns de alterações de código que trazem latência, reduzem a responsividade e adicionam dívida técnica — que terá que ser paga depois.

Mas hoje, vamos falar de coisa boa! Vou trazer algumas ferramentas que podem nos ajudar a experimentar um pouco mais o nosso sistema e até mesmo retirar métricas de versões e criar um comparativo de performance ao longo do tempo. Para manter a sanidade mental de todos, é esperado que já tenha algum conhecimento com testes de API com Postman. Caso não tenha, dê uma olhadinha aqui: https://medium.com/@arthurgomesfaria/automating-apis-testing-with-postman-and-newman-a9a9ef0354d4.

Nesta outra publicação, demonstro como montar um fluxo completo de usuário dentro do Postman e executar os testes através da linha de comando para validar o funcionamento das suas APIs.

Naturalmente, uma publicação no Medium não cobre nem 10% do que todo esse escopo traz. Logo, no final indico dois livros essenciais para quem gostaria de saber um pouco mais sobre Testes de Carga e Performance.

Bom, vamos lá então. Primeiro, vou falar um pouquinho de tipos de teste (uma breve explicação de cada um) e depois montar um exemplo de cada com o postman-to-k6, k6 e um contêiner Node (executando nossa aplicação).

Para montá-los, vou utilizar coleções de Testes do Postman (contendo um fluxo completo de usuário — criação, leitura e exclusão) que foram exportadas em formato JSON e “traduzi-las” com o postman-to-k6 para scripts JS que possam ser executados pelo k6.

Obs: todo o código assim como a coleção Postman utilizada como exemplo pode ser encontrado no repositório: https://github.com/tutagomes/teste-carga-k6-postman

Tipos de Teste

Quando falamos de testes de carga e performance, é fundamental distinguir os modelos de execução. Para cada modelo existe uma metodologia, um fundamento e parâmetros a serem utilizados durante o teste. Por exemplo, um teste de stress coloca muito mais usuários para experimentar suas APIs do que um teste de regressão.

Também é extremamente importante definirmos métricas para avaliar, ou seja, o que para nós é importante (ex: é melhor ter um tempo de resposta máximo de 3 segundos mas só atender 30% das requisições? Ou é melhor ter um tempo máximo de 30 segundos atendendo 100% das requisições?).

Só existe uma coisa em comum em todos eles que já vou adiantando: a execução de aquecimento. Ninguém é louco de correr uma maratona completa sem, pelo menos, dar uma aquecida antes (bom, acho que existem alguns loucos capazes, mas não vêm ao caso). Por isso, antes de cada um, vamos executar um teste bem pequeno. Com apenas um usuário e algumas interações para carregar a aplicação em memória, abrir os canais de comunicação com o banco de dados e permitir uma resposta mais fiel à realidade do sistema.

Carga

https://unsplash.com/photos/CHAFV-0U7b8

Bom, vamos supor que não sabemos quantos usuários nossa aplicação aguenta em um determinado hardware (isso é importante! Pois rodando a mesma aplicação no Celeron do seu Tio Roberto com 2GB de RAM e em um servidor com vários núcleos e muito, mas muito mais RAM teremos resultados completamente divergentes), como podemos estimar até quantas conexões ela aguenta?

E aí entramos com nosso teste de carga. Realizando um fluxo comum de usuário, como um CRUD simples, podemos calibrar quantas conexões a aplicação permite realizar em um determinado período de tempo (normalmente por segundo — rps - requisições por segundo). E, além disso, podemos variar o hardware e verificar como a aplicação se comporta em alguns cenários onde existe mais RAM disponível ou mais CPU.

Aqui estamos preocupados com:

  • Número de usuários concorrentes
  • Tempo de resposta
  • Parte mais lenta do sistema
  • Hardware “ideal”
  • Garantir 100% de resposta

Stress

https://unsplash.com/photos/bbrkZnqNbmk

Tá, e se agora eu quiser ver o que acontece quando o número de usuários é absurdo (para determinado contexto, claro)? Para isso, podemos executar o teste de stress. Diferente do teste de carga onde buscamos visualizar o comportamento da aplicação sobre uso normal, buscando gargalos e lentidões não planejadas, desta vez vamos ver até onde o sistema aguenta.

Aqui, será possível ter uma indicação de qual parte irá falhar mais rápido, qual API irá demorar mais para responder e o que travará primeiro: a aplicação, o banco, os dispositivos de rede ou o Windows (pun intended).

Estamos preocupados com:

  • Quantidade de requisições sem sucesso
  • Tempo médio de resposta
  • Recursos consumidos
  • Primeira falha do sistema
  • Se o sistema retorna ao estado de funcionamento sem intervenção

Regressão

https://unsplash.com/photos/ojrm4kId_MY

E por último (pelo menos neste post), nosso teste de regressão. Muitas vezes é difícil dizer se a mudança que alguém realizou no código fonte ou até mesmo a alteração do hardware em que o sistema é executado gerou impacto significativo na usabilidade/responsividade/comportamento da aplicação. Com esse teste, buscamos executar uma comparação entre diferentes versões do nosso programa, avaliando resultados positivos, negativos ou neutros.

Assim, é possível visualizar tanto o ganho de performance quanto a degradação da mesma ao longo do tempo. Com esses dados em mãos fica fácil identificar alterações ruins (redução de performance) e boas (aumento de performance) feitas no sistema e aprender com elas.

Estamos preocupados com as mesmas questões do teste de carga, mas desta vez, buscando responder as seguintes perguntas:

  • A última alteração do código/da infraestrutura adicionou latência?
  • Criou gargalos?
  • Aumentou ou reduziu a quantidade de RPS?
  • Continua se comportando como esperado?
  • Falha em mais requisições?

Bora Testar

Para facilitar, disponibilizei uma API RESTful completa com NodeJS, Express, TypeORM e Mongo neste repositório. Ele contém todos os arquivos necessários para executar os passos a seguir além de exemplos de coleções do Postman para você criar seus próprios testes.

Para subir os serviços, será necessário ter o docker e docker-compose instalados na máquina.

Vamos então fazer a build da nossa imagem docker contendo nossa API e subir o ambiente completo

docker-compose build
docker-compose --compatibility up -d

A flag — compatibility serve para dizer para o docker-compose utilizar as limitações de CPU e Memória impostas na descrição do serviço. Desta maneira, poderemos alterá-las posteriormente e verificar o que acontece com nosso ambiente à medida que adicionamos mais memória ou processamento.

E claro, para parar:

docker-compose down

Carga

Vamos executar nosso primeiro teste de carga. De maneira bem simples, se acessar o docker-compose.yaml, é possível ver que o nosso serviço do app está limitado a utilizar 1 CPU e 200MB de RAM. Assim, podemos dimensionar nosso teste para que este hardware consiga responder todos nossos usuários.

Para começar, vamos converter nossa coleção de testes Postman para executá-las com o k6 através do comando:

postman-to-k6 Postman-Teste-V1.json -o k6-v1-script.js

Teremos então um script pronto para ser rodado. O próximo passo é dimensionar a quantidade de usuários e o tempo que nosso teste irá durar. Assim, podemos medir quantas requisições são completadas em, por exemplo, 30 segundos e validar se houve algum problema durante o processo.

Executemos então um teste com 50 usuários durante 30 segundos com o comando:

# Não se esqueça do teste de warmup!k6 run --vus 2 --duration 10s k6-v1-script.jsk6 run --vus 50 --duration 30s k6-v1-script.js

O resultado deve ter este formato:

Primeiro Teste com 1 CPU e 200MB

Note que o tempo médio de cada interação, ou seja, Criar Usuário, Ler Usuário e Apagar Usuário é de 163.67ms, num total de 9198 interações. Logo, podemos concluir que durante 30 segundos, 50 usuários completaram 9198 ciclos completos de funcionalidade. Um parâmetro importante também a se notar é as requisições por segundo, que foram de 914.

Agora, vamos alterar alguns parâmetros no nosso docker-compose e ver o que acontece. Sugestão: altere para 2 CPU e 400MB de RAM. Não se esqueça de executar os comandos para parar e subir novamente:

docker-compose down
docker-compose --compatibility up -d
Primeiro Teste com 2 CPU e 400MB

Com os resultados acima, podemos validar que as alterações sugeridas para nosso ambiente surtiram um efeito considerável na nossa aplicação. O tempo médio de interação reduziu de 163ms para 121ms, além de que conseguimos executar 12395 interações completas, atingindo 1236 RPS.

Daqui então surgem perguntas como:

  • Como dimensionar minha máquina de testes?
  • É possível replicar o ambiente de produção para executar tais testes?
  • Como validar o maior gargalo da aplicação?
  • 40ms de tempo médio de resposta é adequado para o meu negócio?
  • 50 usuários é a quantidade esperada de conexões concorrentes?
  • 1236 RPS é o suficiente para atender a minha demanda?
  • Em qual ambiente devo rodar para atingir 2000 RPS?

Essas são perguntas importantes para serem respondidas tanto por você quanto pelas pessoas envolvidas no processo de produção do sistema. Experimente respondê-las e ajustar os testes para refletir melhor o seu contexto!

Stress

Pronto, agora vamos ver como a aplicação se comporta sobre carga pesada. Vamos começar já com 1000 usuários e ver os resultados:

k6 run --vus 1000 --duration 30s k6-v1-script.js
1000 usuários

Que tal subir um pouco mais, para 2000? Naturalmente, esses números são ínfimos se comparados aos usuários de um serviço como o YouTube, Magazine Luiza, AWS. Portanto, é interessante sabermos quantas conexões nosso sistema recebe diariamente para podermos dimensionar corretamente nossos testes.

k6 run --vus 2000 --duration 30s k6-v1-script.js
2000 usuários

Um dos grandes problemas desse tipo de validação é o hardware em que ele é executado. O computador que eu executei por exemplo, teve dificuldade com números de usuários superiores a 2000.

Mas já podemos ter uma visão interessante sobre os números. Note o tempo de resposta basicamente dobrando em contraste com a quantidade de interações por segundo, que aumentou. Ou seja, mesmo executando em um hardware simples, conseguimos continuar atendendo nossos 2000 clientes sem problemas, desde que eles estejam dispostos a esperar 4 segundos para o cadastro por exemplo.

Neste teste, não conseguimos fazer com que nossa aplicação quebrasse, nem extrair muitas informações dos resultados. Por isso ele é um dos mais complexos e difíceis de se executar, normalmente necessitando de hardware potente além de ferramentas de análises de métricas e comportamento do sistema.

Daqui então surgem perguntas como:

  • Como dimensionar minha máquina de testes?
  • 4.48s de tempo médio de resposta é adequado para o meu negócio?
  • Como avaliar o consumo de recursos ao longo do tempo?
  • Existem alertas para quando um serviço para?
  • O que é feito quando um serviço para?
  • Em qual ambiente devo rodar para atingir 15000 RPS?
  • Esse teste revelou algo sobre a minha aplicação que não poderia ser revelado com outros tipos de teste?

Regressão

Vamos executar nosso teste de regressão bem parecido com o de carga, só que desta vez vamos comparar a versão 1 com a 2 das APIs do nosso sistema.

Elas já estão implementadas no código disponibilizado, logo é só executar os testes. Como a coleção do Postman também já está pronta e com o apontamento correto, basta traduzi-la para o k6 com o comando:

postman-to-k6 Postman-Teste-V2.json -o k6-v2-script.js

E não se esqueça de executar nosso teste de warmup!

k6 run --vus 10 --duration 10s k6-v2-script.js

Pronto, agora podemos executar nosso primeiro teste apontando para a versão 1 da API e depois executar apontando para a versão 2.

Versão 1:

k6 run --vus 50 --duration 30s k6-v1-script.js
v1 - 50 usuários, 30s, 2CPU, 400MB de RAM

Versão 2:

k6 run --vus 50 --duration 30s k6-v2-script.js
v2–50 usuários, 30s, 2CPU, 400MB de RAM

Agora vamos avaliar os resultados.

Podemos perceber então que as alterações das APIs resultou em uma gigantesca perda de performance, de até 100x. Perceba como as requisições por segundo foram de 1172 para 17 e as interações de 11758 para 224. Isso demonstra que antes seus 50 usuários conseguiam interagir 11758 vezes, mas agora só conseguem 224 em um período de 30 segundos.

Vamos imaginar que cada interação que estamos medindo aqui fosse de um fluxo de compra completo, desde colocar no carrinho até fechar o pagamento. A atualização da nossa API estaria nos impedindo de faturar 11534 pedidos a cada 30 segundos. Já deu para perceber o impacto que isso pode ter em um e-commerce, né?

E deixo aqui as perguntas que fiz lá em cima:

  • A última alteração do código/da infraestrutura adicionou latência?
  • Criou gargalos?
  • Aumentou ou reduziu a quantidade de RPS?
  • Continua se comportando como esperado?
  • Falha em mais requisições?

Conclusão

Bom, espero que se tenha lido até aqui, possa ter capturado uma visão melhor sobre alguns dos tipos de teste de carga e entendido um pouco sobre ferramentas como o postman-to-k6 e o próprio k6.

O objetivo foi demonstrar através de alguns experimentos como podemos tirar proveito de coleções já criadas no Postman e utilizá-las para retirar métricas e resultados das nossas aplicações, sem necessidade de aprender a escrever um teste específico, uma nova linguagem de programação ou até mesmo a mexer no JMeter (além de ter que acostumar com uma interface anos 2000). Assim, fica até fácil executar alguns testes nas suas APIs próprias.

Além disso, espero ter dado algumas ideias de como implementá-los agora e começar a medir a performance da sua aplicação, principalmente comparando versões diferentes!

Atenção! Pelo amor de <insira divindade aqui>! Não execute testes assim em produção sem o consentimento dos responsáveis da infraestrutura! Você pode causar muitos mais estragos que imagina!

Bibliografia

Código completo: https://github.com/tutagomes/teste-carga-k6-postman

Livro: Performance Testing Guidance for Web Applications — Microsoft

Livro: Software Performance and Scalability: A Quantitative Approach — Henry H. Liu

k6: https://k6.io/

postman-to-k6: https://github.com/loadimpact/postman-to-k6

Postman: https://www.postman.com/

--

--

Arthur Faria
Arthur Faria

Written by Arthur Faria

Coding, coffeeing, rock n' rolling.

No responses yet