O LabNFeChallenger é um laboratório de engenharia de software projetado para simular o processamento de Notas Fiscais Eletrônicas (NF-e) sob condições extremas de estresse de carga e restrição severa de recursos de infraestrutura.
O principal objetivo deste projeto é receber milhares de requisições contendo payloads XML de NF-e, garantir a resiliência dos dados e processá-los de forma assíncrona, simulando uma comunicação real com a SEFAZ que impõe uma latência síncrona obrigatória de 30 segundos por nota.
- Memória Extremamente Limitada: A aplicação Java é forçada a rodar com um limite máximo de 128MB de Heap (
-Xmx128m), impossibilitando o empilhamento de requisições pesadas diretamente na memória síncrona do servidor HTTP. - Gargalo Externo de Tempo: Cada nota precisa obrigatoriamente aguardar uma simulação de rede de 30 segundos (
Thread.sleep(30000)) para espelhar o comportamento do WebService do governo.
Para sobreviver a esse cenário sem estourar a memória do servidor (OutOfMemoryError) e sem travar as requisições dos clientes HTTP, o design do sistema evoluiu drasticamente:
No modelo inicial, as requisições HTTP batiam no Controller e tentavam resolver o processamento de forma acoplada ou utilizando mensageria desalinhada com a infraestrutura:
- Bloqueio de Threads HTTP: O servidor Tomcat tentava gerenciar milhares de conexões abertas simultaneamente, esperando a resposta de 30 segundos da SEFAZ. As threads do Tomcat esgotavam rapidamente.
A arquitetura atual foi completamente remodelada para separar a camada de Ingestão (síncrona/rápida) da camada de Processamento (assíncrona/paralela):
- Desacoplamento Total: O Controller HTTP apenas valida a presença do payload, gera um identificador único (Protocolo UUID) e despacha a mensagem imediatamente para o Apache Kafka. O ciclo de vida do request HTTP dura milissegundos, liberando a thread do Tomcat na hora.
- Paralelismo Real Baseado em Partições: O ecossistema agora gerencia o ciclo de vida do tópico de forma programática através do Spring, forçando o provisionamento de 5 partições balanceadas.
- Consumo Concorrente Escalonado: O Worker de processamento utiliza a propriedade de concorrência ativa (
concurrency = 5), acionando 5 threads independentes e dedicadas em background. Cada thread assume o controle de uma partição do Kafka, permitindo que 5 notas fiscais sejam transmitidas à SEFAZ simultaneamente, multiplicando a vazão do sistema por 5 sem elevar o consumo de memória RAM da aplicação.
No modelo inicial, as requisições HTTP batiam no Controller e tentavam resolver o processamento de forma acoplada:
- Bloqueio de Threads HTTP: O servidor Tomcat tentava gerenciar milhares de conexões abertas simultaneamente...
- Memória: Conforme requisições chegavam eram enfileiradas em memória o que levava ao estouro...
A arquitetura atual foi completamente remodelada para separar a camada de Ingestão da camada de Processamento:
- Desacoplamento Total: O Controller HTTP apenas valida a presença do payload e gera um UUID...
- Paralelismo Real Baseado em Partições: O ecossistema agora gerencia o ciclo de vida do tópico com 5 partições...
- Memória: As requisições agoram ficam armazenadas no Kafka, o que diminue pressão da memória e gera agilidade no processamento.
Para que a arquitetura orientada a eventos consiga operar de forma estável dentro do limite severo de 128MB de RAM, cada componente do ecossistema foi calibrado milimetricamente. Abaixo estão as tecnologias que compõem o laboratório e as otimizações aplicadas.
- Java 21: Utilização da versão LTS mais recente para máxima eficiência de gerenciamento de memória da JVM.
- Spring Boot 3.4.2: Framework base para o desenvolvimento dos microsserviços (Controller, Ingestão e Mensageria).
- Apache Kafka 4.2.1: Configurado em modo KRaft (Single Node), eliminando a complexidade e o overhead de memória do antigo ZooKeeper.
- AKHQ: Interface web administrativa leve para monitoramento de tópicos, partições e comportamento do
Lagdos grupos de consumo. - K6 (Grafana): Ferramenta de teste de carga baseada em scripts JavaScript para injeção massiva de requisições HTTP simulando o ambiente de produção.
O segredo da estabilidade deste laboratório está no arquivo application.properties. As configurações foram divididas estrategicamente em três pilares:
Por padrão, o Spring Boot abre até 200 threads para processar requisições HTTP simultâneas. Em um ambiente com pouca RAM, isso causa estouro de memória imediato devido ao peso de cada stack de thread.
# Reduz o consumo de RAM limitando as threads simultâneas ativas no Tomcat
server.tomcat.threads.max=20
# Mantém a resiliência do teste segurando o excedente do K6 na fila do S.O.
server.tomcat.accept-count=200O impacto: O Tomcat processa até 20 requisições por vez. O restante fica aguardando em uma fila de rede segura gerenciada pelo Sistema Operacional, evitando que a JVM crie threads em excesso e sofra um OutOfMemoryError.
O produtor precisa esvaziar o buffer do Controller o mais rápido possível para liberar as conexões HTTP dos clientes.
# Força o envio IMEDIATO do payload para o broker sem reter pacotes em memória (0ms)
spring.kafka.producer.linger.ms=0
# Exige confirmação apenas do nó líder, minimizando o tempo de espera do request síncrono
spring.kafka.producer.acks=1
# Impede que o Controller fique travado indefinidamente se o cluster Kafka oscilar
spring.kafka.producer.properties.max.block.ms=5000Como o processamento da SEFAZ introduz uma latência artificial de 30 segundos, o consumidor corre o risco de ser expulso pelo Kafka se passar tempo demais preso em um lote grande de mensagens.
# Puxa no máximo 10 mensagens por lote (Evita sufocar o Heap com Strings de XML pesadas)
spring.kafka.consumer.max-poll-records=10
# Eleva o limite de tempo para 10 minutos para que o Java processe o lote sem sofrer timeout
spring.kafka.consumer.properties.max.poll.interval.ms=600000Impacto: O Kafka entende que o Worker está vivo e trabalhando de forma saudável, mesmo processando as mensagens lentamente devido ao gargalo externo do governo, eliminando o fantasma do estado de Rebalancing infinito.
Siga este roteiro cronológico para subir a infraestrutura limpa, ativar a automação do Spring Boot e disparar os testes de carga com o K6.
Antes de começar, certifique-se de ter instalado em sua máquina Linux:
- Docker e Docker Compose
- Java 21 JDK
- K6 (Ferramenta de testes de carga da Grafana)
Para garantir que nenhuma configuração corrompida ou volume fantasma interfira no seu teste, execute a limpeza absoluta do ambiente no terminal:
docker compose down -v --remove-orphans && docker system prune -fCom o ambiente zerado, inicie os containers do Apache Kafka (KRaft) e da interface visual do AKHQ em background:
docker compose up -dAbra o projeto na sua IDE (IntelliJ/Eclipse) ou utilize o terminal para iniciar o Java.
Assim que a aplicação der o Start completo (Started LabNFeChallengerApplication), a classe de configuração programática KafkaConfig entrará em ação automaticamente, criando o tópico nfe-recebidas com as 5 partições perfeitamente calibradas antes do recebimento de qualquer requisição.
Abra uma nova janela no seu terminal e execute o script do K6 para injetar uma carga controlada de usuários simulando o envio simultâneo de XMLs de Notas Fiscais contra o endpoint da Controller:
k6 run nfe-carga.jsO comando acima vai manter varios usuários virtuais batendo na API síncrona sem parar pelo período de x segundos determinados no arquivo nfe-carga.js .
Para validar se o paralelismo e o comportamento das threads estão funcionando conforme o esperado, você dispõe de duas ferramentas de monitoramento:
Você pode interrogar diretamente o coração do Kafka para extrair os metadados brutos do seu grupo de consumo. Execute o comando abaixo no terminal:
docker run --net=host --rm confluentinc/cp-kafka:7.6.0 kafka-consumer-groups --bootstrap-server 127.0.0.1:9094 --describe --group nfe-groupAo executar este comando consecutivamente com um intervalo de alguns segundos, você poderá observar:
-
A coluna CURRENT-OFFSET de todas as partições subindo de forma simultânea.
-
A coluna LAG diminuindo de forma constante, provando que as mensagens estão sendo limpas da fila de maneira distribuída.
Acesse no seu navegador a URL administrativa do painel do AKHQ:
Endereço: http://127.0.0.1:8089
Nesta tela, navegue até o menu de tópicos e observe os dois indicadores principais para validar a matemática do ecossistema:
-
Count: Representa o volume total de payloads que o K6 conseguiu registrar com sucesso no disco do Kafka (o histórico acumulado estático que não diminui).
-
nfe-group Lag (Caixinha Verde): Exibe o saldo exato de notas que ainda aguardam processamento. Ao atualizar a página (F5), você verá esse indicador derreter em blocos de 5 em 5 mensagens por segundo, confirmando a saúde e a alta performance do paralelismo das suas 5 threads de consumo em background.

