Teste de carga de API GraphQL: guia prático
7 min de leitura
Para fazer um teste de carga de API GraphQL, escreva um script com as operações que seus clientes realmente enviam, dê a cada uma um nome próprio na ferramenta de teste e misture-as nas proporções que você vê em produção, com variáveis variadas. Verifique em cada corpo de resposta se existe um array errors, porque um servidor GraphQL costuma responder HTTP 200 mesmo quando parte da operação falhou. Depois aumente a carga e avalie cada operação pelos próprios percentis.
Fontes: graphql.org, o rascunho da especificação GraphQL over HTTP e a documentação de cada ferramenta, tudo conferido em 9 de outubro de 2026.
| Ferramenta | Nomear cada operação com | Reprovar uma resposta com errors | Alimentar variáveis a partir de |
|---|---|---|---|
| k6 | tags: { name: "..." } | Um check() no corpo já parseado | SharedArray |
| JMeter | O nome do sampler | JSON Assertion em $.errors, invertida | CSV Data Set Config |
| Gatling | http("...") ou o plugin de GraphQL | jsonPath("$.errors").notExists() | Feeders |
| Artillery | Um name na requisição | notHasProperty: "errors" | config.payload |
Por que uma API GraphQL precisa de um teste de carga diferente?
Porque todas as operações vão para uma única URL, e o trabalho por trás depende da consulta e das variáveis. Um relatório agrupado por URL coloca uma leitura barata e uma busca cara no mesmo balaio.
O guia Serving over HTTP do graphql.org diz que um servidor GraphQL opera em um único endpoint, normalmente /graphql. As operações chegam ali por POST, com um corpo JSON que traz query, um operationName opcional e variables.
Como obter resultados por operação?
Dê a cada requisição o nome da sua operação GraphQL e faça a ferramenta agrupar as estatísticas por esse nome. Cada ferramenta tem a própria configuração para isso (conferido em 9 de outubro de 2026).
- k6: o guia de tags e grupos orienta a definir
tags.namepara dar um nome significativo à requisição, e os thresholds podem mirar uma única tag, comohttp_req_duration{name:GetProduct}. - JMeter: o Aggregate Report cria uma linha para cada requisição com nome diferente. Desde o JMeter 5.4, o sampler GraphQL HTTP Request tem campos para Query, Variables e Operation Name.
- Gatling: o nome da requisição é a chave das estatísticas, e o plugin de GraphQL nomeia as requisições pela operação, por exemplo
query GetUser. - Artillery: o plugin metrics-by-endpoint reporta por URL e acrescenta à entrada o
nameda requisição, de modo que operações nomeadas aparecem separadas;useOnlyRequestNames: trueremove a URL e mantém só o nome.
Quais operações o teste deve enviar?
As que a produção recebe, nas mesmas proporções. Tire a contagem de cada operação dos logs do servidor ou do seu monitoramento de GraphQL e transforme-a em uma mistura ponderada de leituras e mutações.
Use os documentos que seus clientes enviam, com a profundidade real, e não uma consulta simplificada. Alimente as variáveis a partir de um arquivo de dados, para que o servidor busque muitos registros diferentes em vez de devolver um único resultado do cache. Mantenha realista também o número de operações distintas: a nota técnica da Apollo sobre teste de carga de GraphQL, um documento do próprio fornecedor, explica que cada operação nova é analisada e validada antes de rodar, e desaconselha gerar muito mais operações únicas do que os clientes reais enviam.
Mutações alteram dados, então rode-as onde os dados possam ser restaurados. O guia passo a passo de teste de carga cobre metas e rampa de subida.
Por que uma resposta HTTP 200 ainda pode ser uma falha?
Porque o GraphQL reporta erros dentro do corpo da resposta. Uma requisição pode voltar com status 200, um array errors e dados parciais, e uma verificação de status deixa passar.
A página Response do graphql.org explica que, quando um resolver falha, a execução continua e a resposta traz data junto com errors. O guia de HTTP acrescenta que essa resposta deve receber mesmo assim um status 2xx, já que o HTTP não tem código para sucesso parcial, e que alguns servidores antigos respondem 2xx até para erros de validação. O rascunho GraphQL over HTTP propõe um status próprio, o 294, para sucesso parcial com o tipo de mídia mais novo application/graphql-response+json.
Em um teste de carga isso atrapalha duas vezes. Os erros não entram na conta, e requisições que falham rápido, como operações recusadas por um limite de profundidade, puxam os tempos de resposta para baixo.
Como verificar o array errors em cada ferramenta?
Faça o parse do corpo JSON e reprove a requisição quando houver errors. Cada ferramenta tem um jeito embutido de fazer isso.
No k6, um check no corpo parseado resolve, e um threshold na métrica checks faz a execução falhar, porque checks sozinhos não mudam o código de saída do k6:
import http from "k6/http";
import { check, sleep } from "k6";
import { SharedArray } from "k6/data";
const URL = "https://staging.example.com/graphql";
const ids = new SharedArray("ids", () =>
JSON.parse(open("./product-ids.json")));
const QUERY = `query GetProduct($id: ID!) {
product(id: $id) { id name price }
}`;
export const options = {
vus: 20,
duration: "5m",
thresholds: {
checks: ["rate>0.99"],
"http_req_duration{name:GetProduct}": ["p(95)<400"],
},
};
function parse(res) {
try { return res.json(); } catch (e) { return null; }
}
export default function () {
const id = ids[Math.floor(Math.random() * ids.length)];
const payload = JSON.stringify({
query: QUERY,
operationName: "GetProduct",
variables: { id: id },
});
const res = http.post(URL, payload, {
headers: { "Content-Type": "application/json" },
tags: { name: "GetProduct" },
});
const body = parse(res);
check(res, {
"status is 2xx": (r) => r.status >= 200 && r.status < 300,
"no GraphQL errors": () => body !== null && !body.errors,
});
sleep(1);
}
O SharedArray carrega os IDs uma única vez para todos os usuários virtuais. O try impede que um corpo que não seja JSON, como a página de erro de um proxy, interrompa a iteração.
No JMeter, adicione uma JSON Assertion com o caminho $.errors e marque “Invert assertion”, para que a amostra falhe quando o caminho existir. Um JSON Extractor copia IDs para variáveis usadas na próxima operação.
No Gatling, acrescente .check(jsonPath("$.errors").notExists()), os dois da referência de checks. O plugin de GraphQL verifica o status e os erros por padrão, mas a página de configuração dele limita as execuções na Community Edition a 5 usuários e 5 minutos (conferido em 9 de outubro de 2026). Para testes maiores sem o Gatling Enterprise, use requisições HTTP comuns com esse check.
No Artillery, o plugin expect oferece notHasProperty: "errors", e reportFailuresAsErrors: true leva as expectativas que falharam para o relatório. Um capture com um caminho json passa um valor para a próxima requisição.
O que observar além dos tempos de resposta?
Cache, consultas persistidas e carga do banco de dados por requisição. Qualquer um deles pode deixar um teste mais rápido ou mais lento que a produção por motivos que nada têm a ver com capacidade.
O guia de desempenho do graphql.org observa que consultas enviadas por GET podem usar cache HTTP e CDN, e que consultas persistidas permitem ao cliente enviar um hash em vez do documento completo. Se seus clientes fazem isso, o teste deve fazer também. Se não fazem, um teste que repete uma consulta com as mesmas variáveis mede um cache que o tráfego real não acerta. A nota técnica da Apollo acrescenta que o Apollo Server, o Gateway e o GraphOS Router ativam por padrão as consultas persistidas automáticas.
O mesmo guia descreve o problema N+1: uma lista cujo resolver carrega cada item separadamente transforma uma requisição em muitas consultas ao banco, algo que costuma ser resolvido agrupando-as com uma ferramenta como o DataLoader. Acompanhe as consultas ao banco por segundo ao lado das requisições por segundo; se elas sobem muito mais rápido conforme a carga cresce, desconfie de um resolver. Limites de profundidade, de complexidade e de taxa também podem disparar durante um teste.
De onde deve vir a carga?
De uma única máquina enquanto você monta o script e faz um smoke test, e depois da nuvem, quando precisar de mais carga ou de tráfego vindo de várias regiões.
A LoadFocus roda scripts k6 a partir de 25 regiões de nuvem. Lá, o número de usuários e a duração vêm das configurações do teste, e não das options do script, e cenários com taxa de chegada rodam com um número fixo de usuários em vez de uma taxa fixa de requisições, a diferença explicada no artigo sobre modelo de carga aberto e fechado. A LoadFocus também publica um modelo para endpoints GraphQL com consultas complexas (em inglês).
FAQ
Um teste de carga de GraphQL deve enviar consultas por GET ou por POST? Do jeito que seus clientes enviam. O graphql.org só permite GET para operações do tipo query, e mutações precisam usar POST. Se a produção envia consultas persistidas por GET através de uma CDN, teste esse caminho também.
O mesmo script serve para testar a carga de subscriptions GraphQL? Normalmente não. O graphql.org explica que subscriptions são de longa duração e costumam usar WebSockets ou server-sent events, então precisam de um cenário próprio em uma ferramenta que suporte esse transporte.
Uma resposta parcial deve contar como requisição com falha? Na maioria dos testes, sim, porque o cliente recebeu um erro em parte do que pediu. Se alguns erros são esperados, como resultados não encontrados para registros apagados, verifique esses códigos de erro específicos e reprove o resto.
Leituras relacionadas
- Como fazer um teste de carga: guia passo a passo
- Teste de carga no GitHub Actions: guia prático
- Ferramentas de teste de carga WebSocket: seis opções
- Teste de carga gRPC: ferramentas, configuração e métricas
As afirmações técnicas deste artigo não foram revisadas de forma independente.