Testes de brokers

Teste de carga MQTT com Gatling: configuração, cenário e relatórios

Teste de carga em broker MQTT com Gatling: adicione o plugin MQTT, escreva uma simulação Java com connect, subscribe e checks de publish e leia o relatório.

Atualizado · 9 min de leitura

Teste de carga MQTT com Gatling: configuração, cenário e relatórios

O Gatling consegue fazer teste de carga em um broker MQTT com seu plugin MQTT: você descreve o comportamento de um dispositivo como um cenário em Java, Kotlin ou Scala — connect, subscribe, publish, esperar a mensagem correspondente — e o Gatling injeta usuários virtuais e gera o relatório HTML com percentis de tempo de resposta e throughput. Funciona bem para times que já usam Gatling para HTTP e querem o mesmo fluxo de simulação como código para MQTT.

Antes de começar, entenda o licenciamento: o componente MQTT é distribuído sob a Gatling Enterprise Component License. Localmente, na Community Edition, ele é limitado a 5 usuários virtuais e testes de 5 minutos — suficiente para desenvolver e validar uma simulação. Rodar em escala real exige o Gatling Enterprise. Se isso não servir para você, k6 com xk6-mqtt, JMeter ou emqtt-bench são alternativas open source.

Configuração do projeto

O jeito mais fácil de começar é um dos projetos demo do Gatling para Maven ou Gradle. Depois adicione o plugin MQTT ao lado das dependências padrão do Gatling:

pom.xml (trecho)
<dependency>
  <groupId>io.gatling</groupId>
  <artifactId>gatling-mqtt-java</artifactId>
  <version>3.16.0</version>
  <scope>test</scope>
</dependency>

Use a mesma versão dos demais artefatos do Gatling. Projetos Scala usam gatling-mqtt em vez de gatling-mqtt-java; no Gradle a declaração é gatlingImplementation("io.gatling:gatling-mqtt-java:3.16.0").

A DSL de MQTT não é importada por padrão, então toda simulação precisa de:

Imports
import io.gatling.javaapi.mqtt.*;
import static io.gatling.javaapi.mqtt.MqttDsl.*;

Antes de escrever a simulação, confirme broker, porta e credenciais com um único cliente no cliente MQTT online. Assim você sabe que qualquer falha posterior vem da carga, não da configuração. Use o endpoint compartilhado broker.connectmq.com apenas para validar o cenário; para carga real, aponte .broker(...) e as credenciais para a sua própria instância privada de broker.

O protocolo: broker, credenciais, QoS

O protocol builder guarda as configurações compartilhadas por todos os usuários virtuais:

Configuração do protocolo
MqttProtocolBuilder mqttProtocol = mqtt
    .mqttVersion_5()                         // ou mqttVersion_3_1_1() (padrão)
    .broker("broker.connectmq.com", 1883)    // TLS: porta 8883 + .useTls(true)
    .credentials(System.getenv("MQTT_USER"), System.getenv("MQTT_PASS"))
    .keepAlive(30)
    .qosAtLeastOnce()                        // o padrão é QoS 0
    .cleanSession(true)
    .reconnectAttemptsMax(3)
    .correlateBy(jsonPath("$.id"));

Dois ajustes merecem atenção:

  • Client ID. Se você não definir clientId(...), o Gatling gera um aleatório por usuário virtual — o padrão seguro para testes de carga. Se usar um feeder, garanta valores únicos, senão os usuários vão tomar a sessão uns dos outros.
  • correlateBy. Diz ao Gatling como associar uma mensagem recebida à requisição que a está esperando. Aqui todo payload carrega um campo id, então a mensagem que um usuário recebe pode ser casada com o publish que ele enviou.

Um cenário de telemetria de dispositivo

O cenário abaixo modela um dispositivo: conecta, faz subscribe no próprio topic e depois publica uma leitura por segundo durante um minuto, esperando cada uma voltar pelo broker.

src/test/java/mqtt/MqttTelemetrySimulation.java
package mqtt;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.mqtt.MqttDsl.*;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.mqtt.*;
import java.time.Duration;
import java.util.Iterator;
import java.util.Map;
import java.util.stream.Stream;

public class MqttTelemetrySimulation extends Simulation {

  MqttProtocolBuilder mqttProtocol = mqtt
      .mqttVersion_5()
      .broker("broker.connectmq.com", 1883)
      .credentials(System.getenv("MQTT_USER"), System.getenv("MQTT_PASS"))
      .keepAlive(30)
      .qosAtLeastOnce()
      .correlateBy(jsonPath("$.id"));

  Iterator<Map<String, Object>> devices =
      Stream.iterate(1, i -> i + 1)
          .map(i -> Map.<String, Object>of("deviceId", "dev-" + i))
          .iterator();

  ScenarioBuilder telemetry = scenario("Device telemetry")
      .feed(devices)
      .exec(mqtt("Connect").connect())
      .exec(mqtt("Subscribe").subscribe("testmqtt/gatling/#{deviceId}/data"))
      .repeat(60, "n").on(
          exec(
              mqtt("Publish")
                  .publish("testmqtt/gatling/#{deviceId}/data")
                  .message(StringBody("{\"id\":\"#{deviceId}-#{n}\",\"temp\":21.5}"))
                  .await(Duration.ofSeconds(2))
                  .check(jsonPath("$.temp").exists())
          ).pause(Duration.ofSeconds(1))
      );

  {
    int users = Integer.getInteger("users", 5);
    setUp(
        telemetry.injectOpen(rampUsers(users).during(Duration.ofSeconds(30)))
    ).protocols(mqttProtocol)
     .assertions(
         global().failedRequests().percent().lt(1.0),
         details("Publish").responseTime().percentile3().lt(200)
     );
  }
}

O que cada parte faz:

  • Feeder. Dá a cada usuário virtual um deviceId único, usado no topic e no id da mensagem.
  • repeat(60, "n"). Repete 60 vezes e expõe o contador como #{n}, de modo que cada id de mensagem é único — requisito para a correlação.
  • .await(Duration.ofSeconds(2)). Um check bloqueante: o usuário espera até chegar, em um topic inscrito, uma mensagem com o id de correlação, ou falha após 2 segundos. .expect(...) é a variante não bloqueante; combine com mqtt.waitForMessages() quando quiser publicar várias mensagens antes de esperar.
  • .check(jsonPath("$.temp").exists()). Valida o conteúdo da mensagem casada, não apenas a chegada.
  • Assertions. Reprovam a execução se mais de 1 % das requisições falhar ou se o percentil 95 de Publish passar de 200 ms (percentile3() é o percentil 95 por padrão). Ajuste os dois à sua própria linha de base.

Os topics seguem testmqtt/gatling/<device>/data; se depois você adicionar subscriptions com wildcards, verifique os padrões no topic matcher.

Escolhendo um perfil de injeção

Em uma simulação MQTT, cada usuário virtual é um dispositivo que mantém uma conexão durante todo o cenário, então o perfil de injeção decide quantas conexões existem ao mesmo tempo e com que velocidade elas chegam:

PerfilUse para testarExemplo
atOnceUsers(n)Uma tempestade de reconexões: todos os dispositivos conectam no mesmo instanteatOnceUsers(5)
rampUsers(n).during(d)Uma frota estável entrando no ar; o ponto de partida habitualrampUsers(5).during(Duration.ofSeconds(30))
constantUsersPerSec(r).during(d)Chegada contínua de novos dispositivos (sessões que começam e terminam)constantUsersPerSec(1).during(Duration.ofMinutes(2))
rampUsersPerSec(a).to(b).during(d)Uma taxa de chegada crescente, para achar o ponto em que as conexões ficam lentasrampUsersPerSec(1).to(5).during(Duration.ofMinutes(1))

As conexões simultâneas são aproximadamente a taxa de chegada × a duração do cenário. Com 60 publicações de um segundo por usuário, cada usuário fica conectado por cerca de um minuto, então constantUsersPerSec(10) se estabiliza em torno de 600 dispositivos simultâneos. Tenha essa conta em mente ao dimensionar uma execução e lembre-se de que, na Community Edition, o total continua limitado a 5 usuários.

Você também pode combinar cenários em um único setUp, por exemplo uma rampa de dispositivos de telemetria mais um pequeno grupo de usuários de dashboard inscritos em um wildcard. Cada cenário mantém seus próprios nomes de requisição, então o relatório mostra as duas cargas separadamente.

Executar a simulação

Executar com Maven
export MQTT_USER=username MQTT_PASS=password
./mvnw gatling:test \
  -Dgatling.simulationClass=mqtt.MqttTelemetrySimulation \
  -Dusers=5

Com a Community Edition, mantenha users em 5 ou menos e a duração abaixo de 5 minutos — a execução para nesses limites. Isso basta para provar que cenário, correlação e checks funcionam. Envie o mesmo projeto para o Gatling Enterprise para rodá-lo com milhares de usuários e vários geradores de carga.

Lendo o relatório HTML

No final, o Gatling mostra o caminho para target/gatling/<simulation>-<timestamp>/index.html. O relatório agrupa tudo por nome de requisição, então os nomes em mqtt("…") são os seus títulos:

RequisiçãoO tempo de resposta significaO que observar
ConnectAté o broker aceitar a conexão (CONNACK), TLS incluídoFalhas ou conexões lentas conforme os usuários sobem
SubscribeAté o broker confirmar a subscription (SUBACK)Raramente é gargalo; falhas costumam indicar problemas de ACL
Publish (com await)Do publish até a chegada da mensagem correlacionadaPercentis de latência ponta a ponta e timeouts

Gráficos úteis:

  • Percentis de tempo de resposta ao longo do tempo. Um p95 ou p99 subindo durante o ramp-up mostra onde o broker ou a rede começam a saturar.
  • Requisições por segundo. Compare com a taxa planejada (usuários × 1 msg/s aqui). Se estabilizar abaixo da meta, algo está limitando.
  • Tabela de erros. Timeouts no await significam que as mensagens não voltaram a tempo — verifique QoS, limites de fila e por que mensagens se perdem. Erros de conexão em um número redondo de usuários costumam indicar um limite; decodifique os reason codes do broker com a ferramenta de reason codes MQTT.

Um método mais amplo para separar limites do broker de limites do gerador de carga está em interpretando resultados de benchmark MQTT.

Dicas para simulações realistas

  1. Modele sessões, não requisições. Dispositivos reais ficam conectados; mantenha a conexão aberta durante o loop em vez de conectar a cada mensagem.
  2. Use tamanhos de payload reais. Troque o JSON mínimo por payloads no formato dos seus; confira o tamanho na rede com a calculadora de tamanho de pacote.
  3. Separe fan-out de um para um. Um cenário de dashboard que faz subscribe em testmqtt/gatling/+/data recebe as mensagens de todos os dispositivos; rode-o como cenário próprio para que a carga dele fique visível.
  4. Acompanhe o gerador de carga. Uma JVM saturada infla os tempos de resposta. Verifique CPU e GC na máquina que roda o Gatling.
  5. Versione a simulação. Rode de novo após upgrades e mudanças de configuração do broker para pegar regressões.

Veja o tráfego do Gatling no cliente online

O plugin MQTT do Gatling oferece cenários legíveis e versionados, com correlação e checks que a maioria das ferramentas MQTT não tem. Monte e valide a simulação dentro dos limites da Community Edition, rode em escala onde sua licença permitir e sempre leia os tempos de publish com checks await, porque essa é a latência que seus dispositivos sentem.

Perguntas frequentes

O suporte a MQTT do Gatling é gratuito?

O plugin MQTT (gatling-mqtt-java) é publicado no Maven Central sob a Gatling Enterprise Component License. Na Community Edition ele fica limitado a 5 usuários virtuais e testes de 5 minutos; os limites caem quando a simulação roda no Gatling Enterprise.

O Gatling suporta MQTT 5?

Sim, o protocol builder oferece mqttVersion_3_1(), mqttVersion_3_1_1() (padrão) e mqttVersion_5(). O Gatling avisa que nem todos os recursos do MQTT 5 podem estar disponíveis ainda, então confira a documentação se você depende de algum específico.

Como o Gatling mede o tempo de resposta MQTT?

Connect e subscribe são medidos até o broker responder. No publish, adicione um check await ou expect: o Gatling passa a registrar o tempo até chegar uma mensagem correspondente, o que dá a latência ponta a ponta no relatório.