Seu carrinho está vazio no momento!

Introdução
Prezados engenheiros e engenheiras de software, sejam bem-vindos à nossa octogésima aula! Como seu professor PhD e um especialista mundial em APIs, hoje mergulharemos em um dos tópicos mais decisivos para a robustez e sucesso de qualquer aplicação moderna: o teste de performance.
Imagine que você está construindo uma ponte gigantesca. De que adianta ela ser bela e funcional se desmoronar ao primeiro engarrafamento? Similarmente, nossas APIs devem ser capazes de suportar o fluxo intenso de requisições que virão de milhões de usuários. Sem testes de performance, sua API é como essa ponte sem certificação de carga: uma catástrofe esperando para acontecer em um pico de demanda. É por isso que este conhecimento é vital para a longevidade e confiabilidade das APIs modernas.
Nesta aula, nosso objetivo será desenvolver a habilidade de medir a capacidade de nossas APIs para lidar com cenários de alta carga. Vamos empregar duas das ferramentas mais potentes e amplamente utilizadas na indústria: Artillery e k6. Você aprenderá a configurar, executar e interpretar testes, garantindo que suas APIs Node.js/Express estejam prontas para qualquer desafio. No contexto do ecossistema Node.js/Express, onde a assincronicidade habilita alta escalabilidade, é fundamental validar que essa promessa seja cumprida sob estresse real.
Conceito Fundamental
O teste de performance é o processo de avaliar a velocidade, responsividade e estabilidade de uma aplicação, serviço ou componente de software sob uma determinada carga de trabalho. Ele não busca apenas identificar bugs funcionais, mas sim desvendar gargalos, prever o comportamento do sistema em situações de pico e garantir que a experiência do usuário permaneça excepcional, mesmo em momentos de grande demanda.
Dentro dessa disciplina, encontramos diversas categorias:
- Teste de Carga (Load Testing): Este tipo de teste simula o uso esperado do sistema em condições normais e de pico, mas dentro dos limites projetados. O objetivo é verificar se a API viabiliza o throughput e os tempos de resposta desejados.
- Teste de Estresse (Stress Testing): Aqui, empurramos o sistema além de seus limites normais de operação para verificar o ponto de falha, ou seja, onde a API começa a degradar ou falhar completamente. Isso é relevante para entender a resiliência do sistema.
- Teste de Pico (Spike Testing): Simula um aumento súbito e drástico de usuários em um curto período. É valioso para cenários como lançamentos de produtos ou promoções-relâmpago.
- Teste de Resistência (Soak/Endurance Testing): Mantém uma carga contínua por um longo período (horas ou dias) para identificar problemas como vazamentos de memória ou degradação gradual de performance, que podem não ser aparentes em testes de curta duração.
A terminologia correta da indústria é essencial. Falamos de latência (tempo que uma requisição leva para ser processada), throughput (número de requisições processadas por unidade de tempo, geralmente em requisições por segundo – RPS ou transações por segundo – TPS), concorrência (número de usuários ou requisições ativas simultaneamente), e escalabilidade (capacidade de um sistema de aumentar seu throughput com o acréscimo de recursos).
Casos de uso reais em produção incluem desde a preparação para eventos comerciais como a Black Friday, campanhas de marketing virais, até o lançamento de um novo recurso que pode atrair milhões de acessos. A integração com outras tecnologias é comum: esses testes podem ser parte de pipelines de CI/CD (Continuous Integration/Continuous Delivery) para garantir que cada nova versão mantenha ou melhore a performance, e os resultados são frequentemente alimentados em sistemas de APM (Application Performance Monitoring) para monitoramento contínuo.
As vantagens são claras: previne falhas catastróficas, facilita a otimização da infraestrutura, garante a QoS (Quality of Service) e, acima de tudo, proporciona uma experiência de usuário superior, o que se traduz em maior satisfação e retenção. Contudo, há desvantagens: a configuração pode ser complexa, exige recursos computacionais para simular a carga, e os resultados podem ser enganosos se os cenários de teste não refletirem fielmente o uso real.
Implementação Prática
Vamos construir uma API Node.js/Express para ser o alvo de nossos testes de performance. Em seguida, utilizaremos Artillery e k6 para simular carga contra ela. Para garantir compatibilidade com ambientes como o HostGator Plano M, manteremos a API simples, focando na lógica de roteamento e processamento, sem dependências complexas de banco de dados ou módulos nativos.
Primeiro, crie uma pasta para seu projeto e inicialize o Node.js:
mkdir api-performance-aula
cd api-performance-aula
npm init -y
npm install express morgan dotenv
Agora, crie o arquivo server.js para a nossa API de exemplo:
// server.js
require('dotenv').config(); // Carrega variáveis de ambiente do .env
const express = require('express');
const morgan = require('morgan'); // Para logging de requisições
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware para logging de requisições
// 'combined' para um formato de log detalhado, útil em ambientes de produção
app.use(morgan('combined'));
app.use(express.json()); // Habilita o parsing de JSON no corpo das requisições
// Simula um "banco de dados" em memória para produtos
const produtos = [];
let proximoIdProduto = 1;
// Rota de saúde simples para verificar se a API está no ar
app.get('/', (req, res) => {
res.status(200).json({ mensagem: 'API de Performance online!' });
});
// Rota para listar todos os produtos
app.get('/produtos', (req, res) => {
// Simula um pequeno atraso de processamento para tornar o teste mais interessante
setTimeout(() => {
res.status(200).json(produtos);
}, 50); // Atraso de 50ms
});
// Rota para adicionar um novo produto
app.post('/produtos', (req, res) => {
const { nome, preco } = req.body;
// Validação de entrada robusta
if (!nome || typeof nome !== 'string' || nome.trim() === '') {
return res.status(400).json({ erro: 'O nome do produto é obrigatório e deve ser uma string válida.' });
}
if (!preco || typeof preco !== 'number' || preco <= 0) {
return res.status(400).json({ erro: 'O preço do produto é obrigatório e deve ser um número positivo.' });
}
// Cria o novo produto
const novoProduto = {
id: proximoIdProduto++,
nome: nome.trim(),
preco: parseFloat(preco.toFixed(2)), // Garante duas casas decimais
dataCriacao: new Date().toISOString()
};
produtos.push(novoProduto); // Adiciona ao "banco de dados"
// Simula um pequeno atraso de salvamento
setTimeout(() => {
res.status(201).json({ mensagem: 'Produto criado com sucesso!', produto: novoProduto });
}, 100); // Atraso de 100ms
});
// Middleware de tratamento de erros global
app.use((err, req, res, next) => {
console.error('Erro na aplicação:', err.stack); // Logging profissional
res.status(500).json({
erro: 'Ocorreu um erro interno no servidor. Tente novamente mais tarde.',
detalhes: process.env.NODE_ENV === 'development' ? err.message : undefined // Mostra detalhes em dev
});
});
// Inicia o servidor
app.listen(PORT, () => {
console.log(Servidor rodando em http://localhost:${PORT});
console.log(Para HostGator Plano M, certifique-se de configurar o ambiente NodeJS corretamente.);
});
Crie um arquivo .env na raiz do projeto:
PORT=3000
NODE_ENV=development
Para rodar sua API, execute:
node server.js
Sua API estará disponível em http://localhost:3000.
Para implantar no HostGator Plano M, você precisaria configurar o ambiente Node.js no cPanel, apontando para o seu server.js e instalando as dependências. As ferramentas de teste de performance (Artillery e k6) não rodam no HostGator, mas sim em sua máquina local ou em um servidor de testes separado, apontando para a URL pública de sua API hospedada.
—
Agora, vamos aos testes de performance.
Artillery
Artillery é uma ferramenta poderosa para testes de carga e estresse, com foco em simplicidade e flexibilidade. Ele possibilita a criação de cenários complexos usando arquivos YAML.
- Instalação (global):
npm install -g artillery@latest
- Crie um arquivo de configuração Artillery:
artillery-test.yml
# artillery-test.yml
config:
target: "http://localhost:3000" # URL da sua API (altere para a URL pública no HostGator)
phases:
- duration: 60 # Duração total do teste em segundos
arrivalRate: 10 # 10 usuários virtuais por segundo
rampTo: 50 # Aumenta a taxa de chegada para 50 usuários virtuais por segundo gradualmente
name: "Fase de Aquecimento e Carga"
defaults:
headers:
Content-Type: "application/json"
payload:
path: "produtos.csv" # Arquivo CSV para dados de teste
fields:
- "nome"
- "preco"
scenarios:
- name: "Buscar Produtos e Adicionar Novo Produto"
flow:
- get:
url: "/produtos" # Realiza um GET na rota /produtos
- post:
url: "/produtos" # Realiza um POST na rota /produtos
json:
nome: "{{ nome }}" # Usa dados do CSV
preco: "{{ preco }}" # Usa dados do CSV
- get:
url: "/" # Rota simples de saúde
Para o payload funcionar, crie um arquivo produtos.csv na mesma pasta:
nome,preco
Teclado Mecânico,150.00
Mouse Gamer,80.50
Monitor Ultrawide,1200.00
Fone de Ouvido Bluetooth,250.99
Webcam Full HD,110.00
O Artillery lerá uma linha do CSV para cada requisição POST que precisar dos campos nome e preco.
- Execute o teste:
artillery run artillery-test.yml
O Artillery exibirá um relatório detalhado no console, incluindo RPS, latência e taxa de erros.
Melhores Práticas Enterprise com Artillery:
- Versionamento: Mantenha seus scripts
.ymlem controle de versão (Git). - Ambientes Distintos: Nunca teste de forma agressiva diretamente em produção. Use ambientes de staging ou pré-produção que sejam o mais próximo possível da produção.
- Isolamento: Execute testes de performance em servidores dedicados para evitar que o próprio cliente de teste se torne um gargalo.
- Relatórios Detalhados: Artillery permite integrar com serviços como New Relic ou Prometheus para métricas mais ricas.
—
k6
k6 é uma ferramenta moderna de teste de carga, desenvolvida em Go, mas com scripts de teste escritos em JavaScript. Isso proporciona grande flexibilidade para lógicas de teste complexas, validação de respostas e inclusão de “cheques” para garantir a integridade dos dados.
- Instalação: k6 é distribuído como um binário. Baixe-o do site oficial (k6.io/docs/getting-started/installation/) ou use um gerenciador de pacotes:
No macOS: brew install k6 No Linux (apt): sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 379CE192D401AB61; echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list; sudo apt update; sudo apt install k6 No Windows: choco install k6 ou baixar o executável.
- Crie um script de teste k6:
k6-test.js
// k6-test.js
import http from 'k6/http';
import { sleep, check } from 'k6';
import { SharedArray } from 'k6/data'; // Para carregar dados de forma eficiente
// Carrega dados de produtos de forma síncrona uma vez para todos os VUs
const produtosData = new SharedArray('produtos', function () {
// Retorna um array de objetos, onde cada objeto é um produto
return [
{ nome: 'Cadeira Ergonômica', preco: 450.00 },
{ nome: 'Mesa Elevatória', preco: 900.00 },
{ nome: 'Docking Station', preco: 180.00 },
{ nome: 'Webcam 4K', preco: 220.00 },
{ nome: 'Microfone Condensador', preco: 130.00 },
];
});
export const options = {
vus: 50, // 50 usuários virtuais (Virtual Users)
duration: '1m', // Duração total do teste: 1 minuto
thresholds: {
http_req_duration: ['p(95)<300'], // 95% das requisições devem ser mais rápidas que 300ms
http_req_failed: ['rate<0.01'], // A taxa de falha de requisições HTTP deve ser menor que 1%
'checks': ['rate>0.99'], // 99% dos checks devem passar
},
// Para simular ramp-up/down como no Artillery:
// stages: [
// { duration: '30s', target: 20 }, // Rampa para 20 VUs em 30 segundos
// { duration: '30s', target: 50 }, // Rampa para 50 VUs em 30 segundos
// { duration: '30s', target: 0 }, // Rampa para 0 VUs em 30 segundos
// ],
};
export default function () {
const BASE_URL = 'http://localhost:3000'; // Alterar para a URL pública no HostGator
// Requisição GET para listar produtos
let resGet = http.get(${BASE_URL}/produtos);
check(resGet, {
'GET /produtos status é 200': (r) => r.status === 200,
'GET /produtos contém array': (r) => Array.isArray(r.json()),
});
sleep(0.5); // Espera 0.5 segundos
// Seleciona um produto aleatório para POST
const produtoParaPost = produtosData[Math.floor(Math.random() produtosData.length)];
// Requisição POST para adicionar um produto
let resPost = http.post(
${BASE_URL}/produtos,
JSON.stringify(produtoParaPost),
{
headers: { 'Content-Type': 'application/json' },
}
);
check(resPost, {
'POST /produtos status é 201 ou 400 (se falhar na validacao)': (r) => r.status === 201 || r.status === 400,
'POST /produtos mensagem de sucesso': (r) => r.json() && r.json().mensagem === 'Produto criado com sucesso!',
});
sleep(1); // Espera 1 segundo
}
- Execute o teste:
k6 run k6-test.js
O k6 exibirá um sumário detalhado, incluindo as métricas e o status dos thresholds configurados.
Melhores Práticas Enterprise com k6:
- Thresholds: Utilize
thresholdspara definir metas de performance. Se os limites não forem atingidos, o teste pode falhar no CI/CD. - Cheques e Groups: Use
checkpara validar as respostas da API egrouppara organizar cenários de teste complexos, tornando os relatórios mais inteligíveis. - Dados de Teste: Carregue dados de teste de forma eficiente usando
SharedArrayou geradores de dados para simular cenários mais realistas. - Visualização: Integre o k6 com Grafana ou outras ferramentas para visualizar os resultados dos testes de forma gráfica.
Exercício Hands-On
Agora é sua vez de implementar e solidificar este conhecimento.
Desafio Prático:
- Crie um novo endpoint na sua API (
server.js) chamado/clientes.
Este endpoint deve ter uma rota GET /clientes para listar clientes (pode ser um array em memória, similar aos produtos).
E uma rota POST /clientes para adicionar um novo cliente. A requisição POST deve aceitar nome (string) e email (string, com validação de formato básico de e-mail).
- Desenvolva um script de teste usando Artillery ou k6 (escolha sua ferramenta preferida) que simule o seguinte cenário:
50 usuários virtuais (ou arrivalRate que rampeie até 50).
Duração de 45 segundos.
Cada usuário deve realizar requisições GET /clientes e POST /clientes.
Adicione um threshold (k6) ou verificação (Artillery) para garantir que:
A taxa de erro total seja menor que 2%.
O tempo de resposta médio para todas as requisições seja menor que 400ms.
Solução Detalhada Passo a Passo:
- Modificar
server.js:
Adicione o seguinte código ao seu server.js, abaixo das rotas de produto:
// Simula um "banco de dados" em memória para clientes
const clientes = [];
let proximoIdCliente = 1;
// Função de validação de e-mail simples
const isEmailValid = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
// Rota para listar todos os clientes
app.get('/clientes', (req, res) => {
setTimeout(() => {
res.status(200).json(clientes);
}, 75); // Atraso de 75ms
});
// Rota para adicionar um novo cliente
app.post('/clientes', (req, res) => {
const { nome, email } = req.body;
// Validação de entrada robusta para clientes
if (!nome || typeof nome !== 'string' || nome.trim() === '') {
return res.status(400).json({ erro: 'O nome do cliente é obrigatório e deve ser uma string válida.' });
}
if (!email || typeof email !== 'string' || !isEmailValid(email)) {
return res.status(400).json({ erro: 'O email do cliente é obrigatório e deve ser um formato de email válido.' });
}
const novoCliente = {
id: proximoIdCliente++,
nome: nome.trim(),
email: email.trim(),
dataCriacao: new Date().toISOString()
};
clientes.push(novoCliente);
setTimeout(() => {
res.status(201).json({ mensagem: 'Cliente adicionado com sucesso!', cliente: novoCliente });
}, 120); // Atraso de 120ms
});
Reinicie seu node server.js após salvar as alterações.
- Criar o Script de Teste (Exemplo com k6):
Crie um arquivo k6-clientes-test.js na mesma pasta:
// k6-clientes-test.js
import http from 'k6/http';
import { sleep, check } from 'k6';
import { SharedArray } from 'k6/data';
const clientesData = new SharedArray('clientes', function () {
return [
{ nome: 'Alice Silva', email: '[email protected]' },
{ nome: 'Bruno Costa', email: '[email protected]' },
{ nome: 'Carla Dias', email: '[email protected]' },
{ nome: 'Daniel Souza', email: '[email protected]' },
{ nome: 'Eva Lima', email: '[email protected]' },
];
});
export const options = {
vus: 50,
duration: '45s',
thresholds: {
http_req_duration: ['avg<400'], // Tempo de resposta médio abaixo de 400ms
http_req_failed: ['rate<0.02'], // Taxa de falha abaixo de 2%
'checks': ['rate>0.98'], // 98% dos checks devem passar
},
};
export default function () {
const BASE_URL = 'http://localhost:3000'; // Sua API!
// Requisição GET para clientes
let resGet = http.get(${BASE_URL}/clientes);
check(resGet, {
'GET /clientes status é 200': (r) => r.status === 200,
});
sleep(0.3); // Pequeno atraso
// Seleciona um cliente aleatório para POST
const clienteParaPost = clientesData[Math.floor(Math.random() clientesData.length)];
// Requisição POST para adicionar um cliente
let resPost = http.post(
${BASE_URL}/clientes,
JSON.stringify(clienteParaPost),
{
headers: { 'Content-Type': 'application/json' },
}
);
check(resPost, {
'POST /clientes status é 201': (r) => r.status === 201,
});
sleep(0.7); // Pequeno atraso
}
- Execute o Teste:
k6 run k6-clientes-test.js
Como testar e validar o resultado:
Observe o output no seu terminal. O k6 (ou Artillery) apresentará um sumário ao final do teste.
- Procure as linhas relacionadas a
http_req_durationehttp_req_failed. - Verifique se os
thresholdsdefinidos foram passados ou falharam. Se aparecer✓para os thresholds, parabéns! Sua API está performando conforme o esperado. Se aparecer✗, sua API pode ter um gargalo. - Analise o tempo médio (
avg) e percentis (p(90),p(95),p(99)) para a duração das requisições.
Troubleshooting dos Erros Mais Comuns:
- “API não responde / Conexão recusada”:
Certifique-se de que sua API (node server.js) está realmente rodando e acessível no BASE_URL configurado.
Se estiver testando uma API remota (ex: no HostGator), verifique o firewall e a URL correta.
- “Muitos erros no teste (HTTP 4xx/5xx)”:
Sua API pode estar falhando na lógica de negócio ou validação de entrada. Verifique os logs do seu server.js (com morgan ativo, você terá mais detalhes).
Um 500 indica erro interno; um 400 (Bad Request) indica problema na requisição enviada pelo cliente de teste.
- “Resultados de performance muito ruins (tempos de resposta altos)”:
A API está com um gargalo. Pode ser a lógica de negócio, acesso a dados (mesmo em memória, simula atrasos), ou a infraestrutura do servidor (HostGator Plano M é compartilhado, pode ter limitações).
* Comece investigando sua lógica, removendo ou otimizando operações demoradas.
Próximos Passos Sugeridos:
- Integração CI/CD: Automatize a execução desses testes de performance em seu pipeline de integração contínua para garantir que novas alterações não degradem a performance.
- Monitoramento APM: Utilize ferramentas de Application Performance Monitoring (como New Relic, Dynatrace ou Grafana/Prometheus) para obter insights mais profundos sobre o comportamento da sua API durante os testes e em produção.
- Testes de Performance com Cenários Autenticados: Explore como Artillery ou k6 podem lidar com fluxos de login e tokens de autenticação para testar rotas protegidas.
- Relatórios Visuais: Configure a exportação de dados do k6 para gerar relatórios gráficos e facilitar a análise e comunicação dos resultados.
Parabéns! Você acaba de dar um passo significativo em sua jornada para se tornar um arquiteto de APIs verdadeiramente robustas e performáticas.
🚀 Pronto para a próxima aula?
Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!