← Aprenda grátis // aprenda · mensageria

Mensageria: por que a fila não deixa nada cair

Todo sistema que cresce chega no dia em que um pedaço dele não dá conta do pico. A fila de mensagens é o amortecedor desse dia. Esta página explica o mecanismo, compara os seis nomes que você vai encontrar no mercado e termina com uma fila de verdade rodando na sua máquina.

A régua desta página. As afirmações saem da documentação oficial e dos blogs de engenharia dos próprios produtos, com link e data no fim. Número de benchmark de vendedor está marcado como número de vendedor. E o código com os passos do teste local rodou de verdade antes de publicar, num WSL2 com um RabbitMQ 4.

O problema que a fila resolve

Sem fila, uma venda no seu site vira uma corrente de chamadas presas umas nas outras: o checkout chama o estoque, o estoque chama o e-mail, o e-mail chama a nota fiscal. O cliente fica esperando a corrente inteira. Se um elo engasga, a venda inteira falha. E no pico, algum elo sempre engasga.

A imagem que destrava o conceito é um restaurante. A comanda é a mensagem. A cozinha é o seu serviço. O trilho onde as comandas ficam penduradas é a fila. O garçom não espera o prato sair pra anotar o próximo pedido: pendura a comanda e volta pro salão. A cozinha puxa comanda no ritmo que consegue. Casa cheia enche o trilho, e ninguém perde pedido.

produtor publica e segue fila m3 m2 m1 puxa no seu ritmo consumidor ack: processei, pode apagar

O desenho inteiro em uma frase: quem produz não espera quem consome, e a fila guarda o que ainda não foi processado.

O ack do desenho é o detalhe que separa quem entende fila de quem só usa. O broker (o servidor que guarda e entrega as mensagens) só apaga uma mensagem quando o consumidor confirma que terminou de processar. Consumidor morreu no meio do trabalho, sem confirmar, a mensagem volta pra fila e outro consumidor pega. É esse mecanismo que transforma "o servidor caiu" de pedido perdido em pedido atrasado.

Fila ou log: a divisão que organiza o mercado

Debaixo do nome mensageria vivem dois modelos com consequências bem diferentes. Na fila, a mensagem consumida e confirmada some: ela existia pra virar trabalho e o trabalho foi feito. No log, a mensagem fica gravada e cada consumidor carrega um marcador da própria posição, o offset. A doc do Kafka abre com isso: "Events are not deleted after consumption". Quem apaga é a política de retenção, por idade ou por tamanho, e nunca o ato de ler.

Fila (RabbitMQ, SQS)

A m1 recebeu ack e sumiu. Cada mensagem vira trabalho uma vez.

Log (Kafka, streams)

Tudo fica. O time de faturamento lê do offset 2, o de relatórios lê do 5, e amanhã um time novo relê do zero.

Essa divisão explica por que os produtos coexistem. O RabbitMQ, o broker de filas mais conhecido, ganhou streams (o modelo de log) na versão 3.9 e a própria doc posiciona sem rodeio: "streams were not introduced to replace queues but to complement them". E admite o limite do modelo clássico com uma franqueza rara em documentação: "No persistent queue types are able to deliver throughput that can compete with any of the existing log based messaging systems". Guarde essa frase: ela responde metade das discussões de Kafka contra RabbitMQ da internet.

As garantias, na letra da documentação

Toda conversa séria de mensageria passa por três promessas de entrega. At-most-once: a mensagem chega no máximo uma vez, e pode não chegar. At-least-once: chega com certeza, e pode chegar duplicada. Exactly-once: chega exatamente uma vez, e é aqui que mora o marketing.

O padrão do mercado é at-least-once. É o que o Kafka declara ("Kafka guarantees at-least-once delivery by default"), o que o RabbitMQ entrega quando você usa ack ("Use of acknowledgements guarantees at least once delivery"), o que o SQS e o Pub/Sub prometem nas filas padrão. A consequência prática é uma só e não tem contorno: o seu consumidor precisa ser idempotente, que é o nome técnico de "processar a mesma mensagem duas vezes não pode causar estrago". Cobrança dupla por mensagem duplicada é o bug clássico de quem pulou esse parágrafo.

Minha opinião, depois de ler as seis documentações: trate exactly-once como recurso de escopo estreito, e nunca como promessa geral. O próprio Kafka avisa: "Many systems claim to provide 'exactly-once' delivery semantics, but it is important to read the fine print". A letra miúda de cada um está na tabela.

Uma ressalva antes da tabela. O mercado mistura os nomes: fila, broker, mensageria, streaming, pub/sub. Nesta página, broker é o servidor que guarda e entrega mensagens. O Kafka se apresenta como plataforma de streaming de eventos e o RabbitMQ como message broker, e mesmo assim os dois disputam os mesmos projetos. A tabela compara o que cada doc afirma, produto por produto.

Os seis, lado a lado

produtomodeloentrega padrãoexactly-once?mensagem máxretençãoquem opera
Apache Kafka Log particionado. Consumir não apaga. At-least-once. Ordem garantida só dentro da partição. Dentro do ecossistema Kafka, com transações e produtor idempotente. Pra sistemas externos, depende da cooperação do destino. ~1 MiB por padrão, configurável. 7 dias por padrão, configurável até ilimitada. Você. Gerenciado só por terceiros (Confluent, Amazon MSK).
RabbitMQ Fila com consumo destrutivo, e streams (log) pra complementar. At-least-once com ack ligado. Sem ack, at-most-once. Não oferece. A doc manda deduplicar no consumidor. 16 MiB por padrão, teto de 512 MiB. Fila guarda até consumir (TTL opcional). Stream corta por idade ou tamanho. Você. Gerenciado por terceiros (CloudAMQP, Amazon MQ).
NATS (JetStream) Log com políticas de retenção que o fazem agir como fila. Core: at-most-once. JetStream: at-least-once. Sim, combinando deduplicação (janela padrão de 2 min) e ack duplo. 1 MB por padrão, até 64 MB. A doc desaconselha passar de 8 MB. Sem limite por padrão. A doc pede pelo menos um limite em produção. Você (um binário único). Gerenciado pela Synadia.
Redis Streams Log dentro de uma chave do Redis. At-least-once com consumer groups e XACK. A doc nega no caso geral. O Redis 8.6 trouxe deduplicação na produção. A doc de streams não fixa um teto próprio (não apurado aqui). Sem TTL por mensagem. Corte manual por MAXLEN ou XTRIM. Você, ou Redis Cloud do próprio vendor.
Amazon SQS Fila pura, gerenciada. Standard: at-least-once, pode duplicar e chegar fora de ordem. FIFO promete "exactly-once processing" com dedup de 5 min. Custo: 300 msg/s por partição sem lote, 3.000 com lote. 1 MiB (o antigo teto de 256 KB subiu). Até 2 GB via biblioteca estendida com S3. 4 dias por padrão, máximo de 14. AWS. Não existe self-host.
Google Pub/Sub Pub/sub gerenciado, com replay opcional via retenção. At-least-once, sem garantia de ordem por padrão. Opt-in, só em assinatura pull, na mesma região, e a doc admite latência bem maior. 10 MB. Até 31 dias no tópico. Não-confirmadas ficam 7 dias na assinatura. Google. Não existe self-host.

Tudo acima sai das páginas oficiais de cada produto, consultadas em 04/08/2026. Os links estão na seção de fontes.

E os números de velocidade?

Aqui vai a parte que os comparativos da internet escondem: quase não existe número neutro. O único comparativo com os três grandes lado a lado veio da Confluent, a empresa dos criadores do Kafka, em 2020: Kafka a 605 MB/s de pico contra 38 MB/s do RabbitMQ espelhado, com p99 de 5 ms a 200 MB/s. É benchmark de vendedor medindo o próprio produto, e deve ser lido assim. Do outro lado, o time do RabbitMQ publicou em 2024 os próprios números: perto de 99 mil mensagens por segundo numa fila clássica de nó único, e 112 mil num stream. O LinkedIn, berço do Kafka, publicou em 2014 o histórico de 2 milhões de registros por segundo em 3 máquinas baratas. Redis e Pub/Sub não publicam número absoluto de throughput na doc, e a doc do NATS mostra saídas de exemplo de laptop avisando que são só exemplos. Quem te der uma tabelinha de velocidade sem essas ressalvas está inventando precisão.

Quatro coisas que se repetem por aí e a documentação desmente

1. "Kafka entrega exactly-once, ponto"

A doc do Kafka delimita o escopo: a garantia vale "when reading, processing and writing data on Kafka topics", com transações e leitura read-committed. Pra qualquer sistema externo, "Exactly-once delivery for other destination systems generally requires cooperation with such systems". Seu banco de dados é um sistema externo.

Kafka Design, Message Delivery Semantics. Consultado em 04/08/2026.

2. "Coloquei numa fila, a ordem está garantida"

Três docs oficiais desmentem de uma vez. SQS Standard: "messages may occasionally arrive out of order". Pub/Sub: "By default, Pub/Sub offers at-least-once delivery with no ordering guarantees". Kafka garante ordem só dentro de uma partição. Ordem global custa caro em qualquer um deles: fila FIFO com teto de vazão no SQS, ordering key limitada a 1 MB/s no Pub/Sub, partição única no Kafka.

SQS Standard queues · Pub/Sub Subscription overview · Kafka Introduction. Consultados em 04/08/2026.

3. "Redis Streams já vem durável, é um Kafka grátis"

A doc do Redis manda fazer dever de casa: "AOF must be used with a strong fsync policy if persistence of messages is important", a replicação assíncrona por padrão não garante que o XADD foi replicado, e no failover a promoção de réplica é "best effort". Dá pra usar Streams em produção. Não dá pra usar sem ler essa página antes.

Redis Streams. Consultado em 04/08/2026.

4. "SQS só aceita mensagem de 256 KB"

Envelheceu. A página oficial de cotas hoje diz que o máximo é "1,048,576 bytes (1 MiB)", e com a Extended Client Library o payload vai pro S3 e chega a 2 GB. Metade dos comparativos que circulam ainda repete o limite antigo.

SQS Message quotas. Consultado em 04/08/2026.

O código, na linguagem que você usa

Produtor e consumidor completos contra um RabbitMQ local, nas quatro linguagens. Tudo isto também está pronto pra clonar no GitHub da Xentory: github.com/xentory-com-br/mensageria. Escolha a sua nas abas, que a página inteira acompanha. Três detalhes fazem estes exemplos serem de gente grande e não de slide: a fila é durável (sobrevive a restart do broker), a mensagem é persistente (vai pra disco) e o consumidor confirma com ack manual depois de processar, com prefetch de 1 pra não abocanhar a fila inteira de uma vez.

O produtor

JavaScript

// npm install amqplib
const amqp = require("amqplib");

async function main() {
  const conexao = await amqp.connect("amqp://guest:guest@localhost:5672");
  const canal = await conexao.createChannel();

  await canal.assertQueue("pedidos", { durable: true });
  const corpo = JSON.stringify({ id: 1, prato: "yakisoba" });
  canal.sendToQueue("pedidos", Buffer.from(corpo), { persistent: true });

  console.log("publicado:", corpo);
  await canal.close();
  await conexao.close();
}

main().catch(console.error);

Python

# pip install pika
import json

import pika

conexao = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
canal = conexao.channel()

canal.queue_declare(queue="pedidos", durable=True)
corpo = json.dumps({"id": 1, "prato": "yakisoba"})
canal.basic_publish(
    exchange="",
    routing_key="pedidos",
    body=corpo,
    properties=pika.BasicProperties(delivery_mode=2),
)

print("publicado:", corpo)
conexao.close()

Java

// dependência: com.rabbitmq:amqp-client (Maven Central)
import java.nio.charset.StandardCharsets;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;

public class Produtor {
    public static void main(String[] args) throws Exception {
        ConnectionFactory fabrica = new ConnectionFactory();
        fabrica.setHost("localhost");

        try (Connection conexao = fabrica.newConnection();
             Channel canal = conexao.createChannel()) {
            canal.queueDeclare("pedidos", true, false, false, null);
            String corpo = "{\"id\": 1, \"prato\": \"yakisoba\"}";
            canal.basicPublish("", "pedidos",
                    MessageProperties.PERSISTENT_TEXT_PLAIN,
                    corpo.getBytes(StandardCharsets.UTF_8));
            System.out.println("publicado: " + corpo);
        }
    }
}

C#

// dotnet add package RabbitMQ.Client
using System.Text;
using RabbitMQ.Client;

var fabrica = new ConnectionFactory { HostName = "localhost" };
await using var conexao = await fabrica.CreateConnectionAsync();
await using var canal = await conexao.CreateChannelAsync();

await canal.QueueDeclareAsync("pedidos", durable: true, exclusive: false, autoDelete: false);

var corpo = """{"id": 1, "prato": "yakisoba"}""";
var props = new BasicProperties { Persistent = true };
await canal.BasicPublishAsync(exchange: "", routingKey: "pedidos",
    mandatory: false, basicProperties: props, body: Encoding.UTF8.GetBytes(corpo));

Console.WriteLine($"publicado: {corpo}");

O consumidor

JavaScript

// npm install amqplib
const amqp = require("amqplib");

async function main() {
  const conexao = await amqp.connect("amqp://guest:guest@localhost:5672");
  const canal = await conexao.createChannel();

  await canal.assertQueue("pedidos", { durable: true });
  canal.prefetch(1);

  await canal.consume("pedidos", (msg) => {
    console.log("recebi:", msg.content.toString());
    canal.ack(msg);
  });
  console.log("esperando mensagens (Ctrl+C para sair)");
}

main().catch(console.error);

Python

# pip install pika
import pika


def ao_receber(canal, metodo, propriedades, corpo):
    print("recebi:", corpo.decode())
    canal.basic_ack(delivery_tag=metodo.delivery_tag)


conexao = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
canal = conexao.channel()

canal.queue_declare(queue="pedidos", durable=True)
canal.basic_qos(prefetch_count=1)
canal.basic_consume(queue="pedidos", on_message_callback=ao_receber)

print("esperando mensagens (Ctrl+C para sair)")
canal.start_consuming()

Java

// dependência: com.rabbitmq:amqp-client (Maven Central)
import java.nio.charset.StandardCharsets;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Consumidor {
    public static void main(String[] args) throws Exception {
        ConnectionFactory fabrica = new ConnectionFactory();
        fabrica.setHost("localhost");

        Connection conexao = fabrica.newConnection();
        Channel canal = conexao.createChannel();

        canal.queueDeclare("pedidos", true, false, false, null);
        canal.basicQos(1);
        canal.basicConsume("pedidos", false, (tag, entrega) -> {
            String corpo = new String(entrega.getBody(), StandardCharsets.UTF_8);
            System.out.println("recebi: " + corpo);
            canal.basicAck(entrega.getEnvelope().getDeliveryTag(), false);
        }, tag -> { });

        System.out.println("esperando mensagens (Ctrl+C para sair)");
    }
}

C#

// dotnet add package RabbitMQ.Client
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

var fabrica = new ConnectionFactory { HostName = "localhost" };
await using var conexao = await fabrica.CreateConnectionAsync();
await using var canal = await conexao.CreateChannelAsync();

await canal.QueueDeclareAsync("pedidos", durable: true, exclusive: false, autoDelete: false);
await canal.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false);

var consumidor = new AsyncEventingBasicConsumer(canal);
consumidor.ReceivedAsync += async (_, entrega) =>
{
    Console.WriteLine("recebi: " + Encoding.UTF8.GetString(entrega.Body.ToArray()));
    await canal.BasicAckAsync(entrega.DeliveryTag, multiple: false);
};
await canal.BasicConsumeAsync("pedidos", autoAck: false, consumidor);

Console.WriteLine("esperando mensagens (Ctrl+C para sair)");
await Task.Delay(Timeout.Infinite);

Como rodar

JavaScript

npm install amqplib
node produtor.js
node consumidor.js    # em outro terminal

Python

python3 -m venv venv          # no Debian/Ubuntu, exige o pacote python3-venv
source venv/bin/activate
pip install pika
python3 produtor.py
python3 consumidor.py    # em outro terminal

Java

# num projeto Maven ou Gradle, declare com.rabbitmq:amqp-client e rode pela IDE.
# sem projeto, baixe amqp-client, slf4j-api e slf4j-simple do Maven Central:
javac -cp amqp-client.jar:slf4j-api.jar Produtor.java Consumidor.java
java -cp .:amqp-client.jar:slf4j-api.jar:slf4j-simple.jar Produtor
java -cp .:amqp-client.jar:slf4j-api.jar:slf4j-simple.jar Consumidor    # em outro terminal

C#

dotnet new console -n Produtor
cd Produtor
dotnet add package RabbitMQ.Client
# troque o Program.cs pelo código da aba e rode:
dotnet run

Suba uma fila na sua máquina

Cinco minutos, um pré-requisito: Docker instalado (no Windows, o Docker Desktop com WSL2). O plano é subir um RabbitMQ 4 com painel visual, publicar de verdade, consumir de verdade e provar a tese da página vendo a fila segurar mensagens com o consumidor desligado.

Crie um arquivo chamado docker-compose.yml com este conteúdo:

services:
  rabbitmq:
    image: rabbitmq:4-management
    container_name: fila-local
    ports:
      - "5672:5672"      # porta que as aplicações usam
      - "15672:15672"    # painel web de gestão
  1. Suba o broker. Na pasta do arquivo, rode docker compose up -d. A primeira vez baixa a imagem e leva um minuto ou dois.
  2. Abra o painel. Acesse http://localhost:15672 e entre com usuário guest e senha guest. Esse usuário só funciona a partir da própria máquina, por padrão do RabbitMQ.
  3. Publique. Rode o produtor da sua linguagem, das abas acima. Ele cria a fila pedidos e publica uma mensagem.
  4. Veja a mensagem parada. No painel, aba Queues and Streams, clique em pedidos. O contador Ready mostra 1: a mensagem está guardada, esperando alguém.
  5. Consuma. Rode o consumidor em outro terminal. Ele imprime o conteúdo, confirma com ack e o contador volta a zero.
  6. Prove a tese. Derrube o consumidor com Ctrl+C. Rode o produtor cinco vezes. O painel mostra 5 mensagens acumuladas e nenhum erro em lugar nenhum. Religue o consumidor e veja a fila drenar, uma a uma, sem perder nada. Esse é o comportamento que a corrente de chamadas síncronas do começo da página não tem.

Regra prática pra quem vai levar isso pra produção: troque o guest/guest, não exponha a porta 15672 na internet e escreva todo consumidor idempotente desde o primeiro dia. At-least-once quer dizer que a duplicata vai acontecer. A pergunta nunca é se, e sempre é quando.

Quer repetir o teste com o modelo de log? O quickstart oficial do Kafka mostra o caminho com a imagem Docker oficial em um comando, e o NATS sobe com um binário único. Os dois links estão na trilha de estudo abaixo.

Como escolher, sem torcida

  1. Comece pela pergunta do reprocesso. Se amanhã um time novo vai precisar reler os eventos do zero (auditoria, relatório, bug corrigido que exige reprocessar), você quer um log: Kafka, stream do RabbitMQ, JetStream. Se cada mensagem morre depois de virar trabalho feito, fila resolve e é mais simples de operar.
  2. Conte quem vai operar. Sem time de infra, o caminho racional é gerenciado: SQS ou Pub/Sub na nuvem que você já usa, ou Confluent e CloudAMQP se quiser Kafka e RabbitMQ sem carregar o cluster nas costas. A doc do Kafka descreve expansão de cluster com migração de partições iniciada manualmente. Leia isso como um aviso de custo de operação.
  3. Se o volume é modesto e o Redis já está no seu stack, Streams aguenta o recado, desde que você faça o dever de casa do AOF que a doc exige.
  4. Escreva idempotente em qualquer um deles. A escolha do broker muda a probabilidade da duplicata, e nunca muda a sua obrigação de sobreviver a ela.
  5. Desconfie de tabela de throughput sem fonte e sem data. Inclusive das que citam esta página.

E a minha aposta pra quem está começando: RabbitMQ local, exatamente como no passo a passo acima. Sobe num comando, o painel mostra a fila acontecendo na sua frente, e o conceito de ack aparece na primeira hora de uso.

Pra continuar estudando

Tudo aqui foi conferido link a link em 04/08/2026. Grátis, salvo indicação.

Documentação oficial, mão na massa

  • RabbitMQ Tutorials: a melhor porta de entrada de todas. Do hello world até confirmação de publicação, em Python, Java, C#, JavaScript, Go e mais.
  • Apache Kafka Quickstart: sobe o broker (inclusive via Docker), cria tópico e produz e consome no terminal.
  • NATS by Example: exemplos executáveis de pub/sub, request-reply e JetStream em várias linguagens.
  • Redis Streams: o tutorial oficial de XADD, XREAD e consumer groups. A mesma página que este artigo cita nas ressalvas de durabilidade.

Em português

Em inglês

  • RabbitMQ Crash Course, Hussein Nasser: 43 minutos do zero ao publisher e consumer em Node.
  • Confluent Developer: os cursos Apache Kafka 101 e Kafka Streams 101, grátis e sem paywall. Feitos pela empresa do Kafka, então leia o entusiasmo com essa lente.
  • ByteByteGo: a newsletter de system design do Alex Xu, com camada grátis.
  • Blog da CloudAMQP: guias de RabbitMQ escritos por quem hospeda RabbitMQ como negócio.

As duas leituras que formam a base

O que fazer com essa informação

  • Se você está aprendendo: rode o passo a passo local hoje. Ver a fila segurar as 5 mensagens com o consumidor morto ensina mais que qualquer parágrafo desta página.
  • Se você decide arquitetura no trabalho: use a tabela e os quatro desmentidos como material de reunião, com os links oficiais prontos pra colar na discussão.
  • Se você estuda pra entrevista: a trilha acima na ordem: tutorial oficial do RabbitMQ, capítulo 11 do Kleppmann, The Log. Com isso você discute mensageria acima da média do mercado.

Fontes

Três honestidades sobre o levantamento. Os números de throughput do NATS na doc oficial são saídas de exemplo da ferramenta nats bench num laptop, e a própria página avisa que são só exemplos, então esta página não os coloca na tabela. O capítulo 11 do livro do Kleppmann foi confirmado por notas de leitura de terceiros porque o sumário oficial da editora bloqueia acesso automatizado. E não achamos benchmark de throughput publicado nas docs oficiais de Redis Streams e Pub/Sub, então nenhum número deles aparece aqui.

Achou imprecisão? Fala com a gente que a gente corrige e registra a correção nesta página.

O próximo estudo, com as fontes na mesa.

Cada conceito de sistema destrinchado assim, com documento oficial, data em tudo e código que rodou de verdade. Chega no seu e-mail junto com o aviso de curso gratuito novo no ar.

Seu e-mail é usado só pra isso. Dá pra sair em um clique em qualquer mensagem, como está na política de privacidade.