Design clássico · Sênior
Fila de mensagens distribuída (estilo Kafka)
Projeto de entrevista clássico. Pontos centrais: storage engines (LSM).
Resposta curta
Projeto de entrevista clássico. Pontos centrais: storage engines (LSM). Uma fila de mensagens separa quem produz de quem consome. O produtor entrega a mensagem e segue; o consumidor busca no seu ritmo. Esse buffer no meio é o que permite que dois sistemas evoluam, escalem e falhem de forma independente.
01 · CONTEXTO E REQUISITOS
Por que uma fila distribuída existe
Uma fila de mensagens separa quem produz de quem consome. O produtor entrega a mensagem e segue; o consumidor busca no seu ritmo. Esse buffer no meio é o que permite que dois sistemas evoluam, escalem e falhem de forma independente.
O desenho do Kafka partiu de requisitos bem concretos: alta vazão para fluxos de eventos de grande volume, como agregação de logs; tolerância a grandes backlogs, para cargas periódicas vindas de sistemas offline; e entrega de baixa latência para usos de mensageria tradicional.
O produtor entrega a mensagem à fila e segue em frente; ele não espera o processamento.
A fila guarda as mensagens na ordem de chegada. É esse buffer que absorve picos e backlog.
O consumidor busca mensagens quando tem capacidade; se atrasar, a fila acumula em vez de perder.
02 · O LOG
Um log em disco, só com acréscimos
A ideia central é guardar a fila como um log: as mensagens são sempre acrescentadas no fim de um arquivo. A documentação do Kafka descreve que uma fila persistente pode ser construída sobre leituras e acréscimos simples a arquivos, e que nessa estrutura todas as operações são O(1) e leituras não bloqueiam escritas.
Disco parece lento por causa do acesso aleatório. A documentação mede a diferença: no arranjo que ela cita, escrita linear chega a cerca de 600 MB/s, contra cerca de 100 kB/s de escrita aleatória, uma diferença de mais de 6000 vezes. Por isso um disco bem usado pode ser tão rápido quanto a rede, e o Kafka depende fortemente do sistema de arquivos.
| Critério | Escrita linear (acréscimo) | Escrita aleatória |
|---|---|---|
| Vazão medida na documentação | Cerca de 600 MB/s | Cerca de 100 kB/s |
| Operação sobre o log | Acrescentar no fim, em tempo constante | Não se aplica ao log |
Dois truques completam o ganho. Primeiro, o agrupamento em lotes: o produtor reúne mensagens, comprime o lote e o servidor o anexa de uma vez, o que transforma rajadas de escritas aleatórias em escritas lineares. Segundo, o cache de páginas do sistema operacional: com consumidores em dia, as leituras saem do cache e o disco quase não é lido.
03 · PARTIÇÕES E CONSUMIDORES
Escalar a leitura sem perder a ordem
Um único log não escala além de uma máquina. A saída é dividir o tópico em partições, cada uma um log independente. O cliente produtor escolhe a partição, ao acaso ou por uma função semântica sobre a chave, e a ordem total existe dentro de cada partição, não entre elas.
Os consumidores se organizam em grupos. Dentro de um grupo, cada partição é lida por exatamente um consumidor por vez; assim, o paralelismo máximo de um grupo é limitado pelo número de partições.
O cliente produtor decide em qual partição cada mensagem entra, ao acaso ou por uma função sobre uma chave.
Cada partição é um log ordenado. A ordem existe dentro da partição, não entre partições.
Dentro do grupo, cada partição tem um único consumidor por vez; um consumidor pode ficar com várias partições.
O consumidor guarda só um inteiro por partição, o offset. Atrasar é inofensivo, e voltar a um offset antigo permite reprocessar.
A posição de leitura é só um inteiro por partição, o offset da próxima mensagem. O consumidor informa o offset a cada requisição e recebe um trecho do log a partir dali. Como ele puxa os dados, um consumidor lento apenas atrasa e alcança depois, e pode também voltar a um offset antigo e reconsumir.
04 · REPLICAÇÃO E FALHAS
Sobreviver à queda de um broker
Cada partição é replicada em vários brokers, e a unidade de replicação é a própria partição. Toda escrita vai ao líder; os seguidores puxam o log do líder e o aplicam ao seu. O Kafka assume replicação por padrão: um tópico sem réplicas é só um tópico com fator de replicação um.
O líder acompanha o conjunto de réplicas em sincronia (ISR). Uma mensagem só é considerada confirmada quando todas as réplicas desse conjunto a aplicaram. Com esse modelo e f+1 réplicas, o tópico tolera f falhas sem perder mensagens confirmadas; um quórum de maioria exigiria três cópias para uma falha e cinco para duas.
O produtor escreve direto no líder da partição, sem camada de roteamento no meio.
Os seguidores puxam o log do líder e aplicam as mensagens ao próprio log.
Só as réplicas em sincronia contam para confirmar a mensagem. Um seguidor que atrasa demais pode ser removido desse conjunto.
O líder cai. Escritas nessa partição ficam sem destino até haver um novo líder.
O controlador percebe a perda da sessão do broker, e um seguidor do conjunto em sincronia é eleito. Ele já tem todas as mensagens confirmadas.
Produtor e consumidor consultam os metadados, descobrem o novo líder e retomam o trabalho.
| Critério | Só eleição entre réplicas em sincronia | Eleição não limpa permitida |
|---|---|---|
| Se todas as réplicas caem | A partição fica indisponível até o último líder voltar | Aceita assumir com uma réplica fora do conjunto |
| Prioridade | Durabilidade e consistência | Disponibilidade, para casos em que ficar no ar é preferível à consistência |
05 · ENTREGA E RETENÇÃO
Duplicata, perda e o que guardar
Confirmar entrega esbarra num dilema. Se o broker marcar a mensagem como consumida ao entregá-la e o consumidor falhar, a mensagem se perde. Se o consumidor processar e falhar antes de confirmar, a mensagem é consumida duas vezes. Do lado do produtor, reenviar após um erro pode gravar a mensagem de novo.
| Semântica | Como se obtém | Risco |
|---|---|---|
| No máximo uma vez | Desligar retentativas no produtor e confirmar o offset antes de processar | Mensagem perdida |
| Pelo menos uma vez | Reenviar até confirmar e confirmar o offset só depois de processar | Mensagem duplicada |
| Sem duplicatas no log | Produtor idempotente, que impede entradas duplicadas ao reenviar | Não cobre sozinho o processamento do consumidor |
O padrão do Kafka é entregar pelo menos uma vez; no máximo uma vez é uma opção. Por isso o consumidor precisa tolerar repetição: o desenho clássico é torná-lo uma operação idempotente, por exemplo descartando um identificador já visto.
Por fim, guardar tudo para sempre não cabe. A retenção simples descarta dados antigos depois de um período fixo ou quando o log atinge um tamanho limite. Quando importa só o estado mais recente de cada chave, a compactação de log mantém ao menos o último valor de cada chave dentro de uma partição.
Perguntas frequentes
O que é Fila de mensagens distribuída (estilo Kafka)?
Projeto de entrevista clássico. Pontos centrais: storage engines (LSM).
O que o design de Fila de mensagens distribuída (estilo Kafka) cobre?
Ele está organizado nestas seções: Por que uma fila distribuída existe, Um log em disco, só com acréscimos, Escalar a leitura sem perder a ordem, Sobreviver à queda de um broker e Duplicata, perda e o que guardar.
Quanto tempo leva Fila de mensagens distribuída (estilo Kafka) e qual é o nível?
Leva cerca de 16 minutos para ler e tem nível Sênior.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.