O k6 consegue fazer teste de carga em um broker MQTT por meio da extensão xk6-mqtt: você escreve o teste em JavaScript com um Client no estilo MQTT.js, o k6 o executa com centenas ou milhares de usuários virtuais e os thresholds transformam o resultado em um check de aprovado/reprovado que você roda no CI. É uma ótima escolha quando você quer testes de carga como código, versionados junto com a aplicação.
Este guia mostra como obter um binário do k6 com suporte a MQTT, o modelo de event loop que derruba a maioria dos primeiros scripts, um script de carga com publishers, subscribers e uma métrica de latência ponta a ponta, e thresholds que reprovam a execução quando o broker fica lento.
Obter o k6 com suporte a MQTT
O MQTT não faz parte do binário principal do k6. Ele vem da extensão xk6-mqtt, mantida pela Grafana e importada como k6/x/mqtt. Há duas formas de usá-la:
Resolução automática de extensões (k6 v1.2.0 em diante). Importe o módulo no script e rode normalmente; o k6 detecta o import e provisiona a extensão:
k6 version # precisa da v1.2.0 ou mais recente
k6 run mqtt-smoke.jsBinário próprio com xk6. Útil em CI sem acesso à internet ou quando você quer fixar a versão exata da extensão:
go install go.k6.io/xk6/cmd/xk6@latest
xk6 build --with github.com/grafana/xk6-mqtt
./k6 run mqtt-smoke.jsPrimeiro, um smoke test
Comece com um único usuário virtual que conecta, faz subscribe, publica uma mensagem e desconecta quando ela volta. Se isso falhar, um teste de carga só vai falhar mais rápido.
import { Client } from "k6/x/mqtt";
const BROKER = __ENV.MQTT_URL || "mqtt://broker.connectmq.com:1883";
export default function () {
const client = new Client({
username: __ENV.MQTT_USER,
password: __ENV.MQTT_PASS,
});
const topic = "testmqtt/k6/smoke";
client.on("connect", () => {
client.subscribe(topic, { qos: 1 });
client.publish(topic, "olá do k6", { qos: 1 });
});
client.on("message", (t, payload) => {
console.log(`recebido em ${t}: ${new TextDecoder().decode(payload)}`);
client.end();
});
client.on("error", (err) => {
console.error(`${err.method}: ${err.message}`);
client.end();
});
client.connect(BROKER);
}MQTT_USER=username MQTT_PASS=password k6 run mqtt-smoke.jsDeixe o cliente MQTT online inscrito em testmqtt/k6/# enquanto roda: você verá a mensagem chegar, o que confirma topic e credenciais independentemente do k6. O endpoint compartilhado broker.connectmq.com serve para este smoke test; rode o script de carga abaixo contra a sua própria instância privada de broker definindo MQTT_URL e as credenciais.
O modelo de event loop
Esta é a parte que difere dos testes HTTP. Com o xk6-mqtt, uma iteração não termina quando a função default retorna. O VU continua executando o event loop — tratando os callbacks de connect, message e dos timers — até a conexão fechar, seja porque você chamou client.end(), seja porque o broker a derrubou.
Consequências para os seus scripts:
- Uma iteração = uma sessão MQTT. Decida quanto tempo dura uma sessão e encerre-a explicitamente com
setTimeout(() => client.end(), …). - Use
setIntervaldentro do handlerconnectpara publicar em ritmo fixo.sleep()bloquearia o event loop. - Sempre trate
errore chameclient.end()ali; senão, um VU com falha pode ficar travado até o graceful stop do cenário. - Dê a cada conexão um
client_idúnico (ou deixe vazio para o broker atribuir um). Duplicatas causam session takeovers que parecem desconexões aleatórias.
Um script de carga com latência ponta a ponta
As métricas embutidas contam conexões, publicações e mensagens recebidas e medem o tempo de conexão, mas não dizem quanto tempo uma mensagem levou do publisher ao subscriber. Adicione isso você mesmo: coloque Date.now() no payload e registre a diferença em uma Trend personalizada. Publisher e subscriber rodam no mesmo VU, então compartilham o mesmo relógio.
import { Client } from "k6/x/mqtt";
import { Trend, Counter } from "k6/metrics";
import exec from "k6/execution";
const BROKER = __ENV.MQTT_URL || "mqtt://broker.connectmq.com:1883";
const SESSION_MS = 30000; // uma iteração = uma sessão de 30 s
const INTERVAL_MS = 100; // 10 mensagens por segundo por VU
const e2eLatency = new Trend("mqtt_e2e_latency", true);
const mqttErrors = new Counter("mqtt_errors");
export const options = {
scenarios: {
telemetry: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "30s", target: 100 },
{ duration: "3m", target: 100 },
{ duration: "15s", target: 0 },
],
gracefulRampDown: "35s",
},
},
thresholds: {
mqtt_e2e_latency: ["p(95)<100", "p(99)<250"],
mqtt_connect_duration: ["p(95)<1000"],
mqtt_errors: ["count<1"],
},
};
export default function () {
const vu = exec.vu.idInTest;
const topic = `testmqtt/k6/${vu}/data`;
const client = new Client({
client_id: `k6-${vu}-${exec.vu.iterationInScenario}`,
username: __ENV.MQTT_USER,
password: __ENV.MQTT_PASS,
});
client.on("connect", () => {
client.subscribe(topic, { qos: 1 });
let seq = 0;
const timer = setInterval(() => {
const body = JSON.stringify({ seq: seq++, t: Date.now(), pad: "x".repeat(200) });
client.publish(topic, body, { qos: 1 });
}, INTERVAL_MS);
setTimeout(() => {
clearInterval(timer);
client.end();
}, SESSION_MS);
});
client.on("message", (t, payload) => {
const msg = JSON.parse(new TextDecoder().decode(payload));
e2eLatency.add(Date.now() - msg.t);
});
client.on("error", (err) => {
mqttErrors.add(1, { method: err.method });
client.end();
});
client.connect(BROKER, { keepalive: 30 });
}Com 100 VUs × 10 mensagens por segundo, a meta é 1.000 mensagens publicadas e 1.000 entregues por segundo. O payload tem cerca de 250 bytes; ajuste pad para ficar igual aos seus dispositivos e confira o tamanho real na rede com a calculadora de tamanho de pacote.
gracefulRampDown fica um pouco acima da duração da sessão para que os VUs terminem a sessão atual durante o ramp-down em vez de serem cortados no meio, o que apareceria como mensagens perdidas.
Thresholds que reprovam a execução
Os thresholds fazem o k6 sair com código diferente de zero quando uma condição é violada — exatamente o que você quer em um pipeline:
| Threshold | Significado |
|---|---|
mqtt_e2e_latency: p(95)<100 | 95 % das mensagens entregues em menos de 100 ms |
mqtt_e2e_latency: p(99)<250 | A latência de cauda fica abaixo de 250 ms |
mqtt_connect_duration: p(95)<1000 | Conexões (incluindo TLS) concluídas em até 1 s |
mqtt_errors: count<1 | Qualquer erro MQTT reprova o teste |
Os valores acima são exemplos. Defina-os a partir da sua própria linha de base: rode o teste com o broker ocioso, anote p95/p99 e deixe uma margem. Compare também os contadores embutidos mqtt_messages_sent e mqtt_messages_received no resumo final — com QoS 1 e um subscriber por topic, eles devem ficar próximos; uma diferença crescente significa mensagens enfileiradas ou perdidas. Para uma análise mais profunda, veja interpretando resultados de benchmark.
Escolhendo um executor
Como uma iteração é uma sessão MQTT, o executor decide quantas conexões ficam abertas e com que frequência chegam novas:
| Executor | Modela | Observação |
|---|---|---|
constant-vus | Uma frota fixa de dispositivos sempre conectados | Conexões simultâneas = VUs |
ramping-vus | Uma frota entrando no ar em etapas | É o do script acima; suba devagar para ver onde a latência dispara |
constant-arrival-rate | Novas sessões começando em ritmo fixo | Defina preAllocatedVUs ≥ taxa × duração da sessão, senão o k6 fica sem VUs |
per-vu-iterations | Cada VU executa exatamente N sessões | Útil para testes de reconexão: sessões curtas, muitas iterações |
Para um teste de tempestade de reconexões, reduza SESSION_MS para poucos segundos e use constant-arrival-rate: cada iteração passa a fazer um ciclo completo de CONNECT, SUBSCRIBE e DISCONNECT, e mqtt_connect_duration mostra como o tempo de conexão piora conforme a taxa de chegada sobe. Veja tempestades de reconexão para saber o que observar.
TLS e WebSockets
Troque o esquema da URL para testar outros transportes com o mesmo script:
k6 run -e MQTT_URL=mqtts://broker.connectmq.com:8883 mqtt-load.js
k6 run -e MQTT_URL=wss://broker.connectmq.com:8084/mqtt mqtt-load.jsO TLS usa a configuração TLS padrão do k6 (por exemplo tlsAuth para certificados de cliente). Comparar o mesmo cenário em TCP e TLS é o jeito mais rápido de ver quanto o handshake custa — veja teste de desempenho TLS no MQTT.
Rodando o k6 no CI
- Mantenha o
mqtt-smoke.jscomo verificação rápida em todo pull request e rode omqtt-load.jstoda noite ou antes de releases. - Passe as credenciais como secrets via
-e MQTT_USER=… -e MQTT_PASS=…ou variáveis de ambiente; nunca as deixe fixas no código. - Exporte resultados com
--summary-export=summary.jsonou uma saída como--out json=results.jsonpara manter um histórico. - Rode o gerador de carga na mesma região do endpoint do broker dos seus dispositivos e acompanhe a CPU; um gerador saturado infla a latência.
Veja testes MQTT no GitHub Actions para um workflow completo.
Veja o tráfego do k6 no cliente online
O k6 com xk6-mqtt transforma um teste de carga MQTT em um script curto e revisável, com critérios claros de aprovação. Acerte o modelo de event loop, meça você mesmo a latência ponta a ponta e deixe os thresholds avisarem quando uma mudança no broker deixou tudo mais lento.
Perguntas frequentes
O k6 suporta MQTT nativamente?
O MQTT vem da extensão xk6-mqtt, importada como k6/x/mqtt. Versões recentes do k6 (v1.2.0 em diante) resolvem extensões oficiais automaticamente quando o script as importa; caso contrário, compile um binário próprio com xk6 build --with github.com/grafana/xk6-mqtt.
Por que exemplos antigos de MQTT no k6 usam outra API?
Vários projetos da comunidade também se chamavam xk6-mqtt e usavam APIs baseadas no construtor. A extensão atual da Grafana usa um Client no estilo MQTT.js, com connect, subscribe, publish e handlers on(event). Não misture exemplos das duas.
Como medir a latência MQTT no k6?
Coloque Date.now() em cada payload, faça subscribe no mesmo topic dentro do mesmo VU e adicione a diferença a uma métrica Trend personalizada quando a mensagem chegar. Depois defina um threshold de p(95) ou p(99) nessa Trend.



