O que acontece depois que um produtor Kafka chama send()?
Isso parece uma pergunta simples. O produtor cria um registro, envia-o ao Kafka e recebe uma confirmação.
No entanto, muita coisa pode acontecer entre esses passos.
O produtor pode esperar por mais registros e criar um batch. Ele precisa escolher uma partição. Ele pode comprimir o batch. Se o broker estiver lento, o produtor pode ficar sem espaço no buffer. Se uma requisição falhar, ele pode tentá-la novamente. Se a retentativa for bem-sucedida depois que a primeira requisição já foi escrita, o Kafka pode receber o mesmo batch duas vezes.
O Kafka Simulator v1.1 foca nessa semântica do produtor.
A versão adiciona 10 novos cenários, elevando o currículo completo para 23 de 125 cenários.
O que está incluído na v1.1?
Oito dos dez cenários focam no produtor. Os outros dois estendem a trilha de anatomia do log: a leitura até o high watermark e o log start offset.
Os cenários do produtor cobrem cinco áreas:
- batching com
linger.msebatch.size, - particionadores do produtor,
- compressão,
- backpressure com
buffer.memoryemax.block.ms, - retentativas e idempotência.
Vamos analisar cada uma delas.
Batching com linger.ms e batch.size
Um produtor Kafka não envia necessariamente cada registro em uma requisição separada.
Registros atribuídos à mesma partição podem ser reunidos em um batch. O produtor envia o batch quando ele fica cheio ou quando o tempo de espera configurado expira.
É aqui que duas configurações do produtor se tornam importantes:
batch.sizelimita o tamanho de um batch,linger.mspermite que o produtor espere por registros adicionais antes de enviá-lo.
O compromisso é entre latência e vazão.
Um batch maior geralmente significa menos requisições e melhor compressão. No entanto, esperar o batch encher pode aumentar a latência de um registro individual.
Os novos cenários tornam esse comportamento visível. Você pode ver quando os registros entram em um batch, o que faz o batch ser enviado e como alterar a configuração afeta as requisições produzidas pelo cliente.
Particionadores do produtor
Antes que o produtor possa criar um batch específico de partição, ele precisa selecionar uma partição.
Para registros com chave, a chave é usada para escolher a partição. Registros com a mesma chave são, portanto, normalmente atribuídos à mesma partição.
Para registros sem chave, o comportamento é diferente. O produtor pode agrupar registros usando sua estratégia de particionamento em vez de calcular a partição a partir de uma chave.
O simulador mostra ambos os casos passo a passo:
- o produtor recebe o registro,
- o particionador seleciona uma partição,
- o registro é adicionado ao batch correspondente,
- o batch é enviado ao líder daquela partição.
Isso facilita entender por que o particionamento afeta tanto a ordenação quanto o batching.
A compressão acontece por batch
A compressão do produtor Kafka é aplicada a batches de registros, não de forma independente a cada registro.
Essa distinção importa.
A compressão costuma ser mais eficaz quando um batch contém múltiplos registros semelhantes. Um batch que contém apenas um registro pequeno pode ganhar muito pouco com a compressão. Um batch maior com dados repetitivos pode ser comprimido de forma muito mais eficiente.
Como resultado, a compressão está conectada à configuração de batching. Configurações como batch.size e linger.ms podem influenciar não só a frequência das requisições, mas também o formato e a compressibilidade dos batches resultantes.
Os novos cenários mostram os tamanhos dos batches não comprimidos e comprimidos, tornando essa relação explícita.
Backpressure com buffer.memory e max.block.ms
O que acontece quando uma aplicação produz registros mais rápido do que o Kafka consegue aceitá-los?
O produtor não pode manter um número ilimitado de registros não enviados na memória. Ele usa um buffer limitado, configurado por meio de buffer.memory.
Quando o buffer não tem espaço disponível, uma chamada a send() pode bloquear enquanto espera que memória seja liberada.
Ela não esperará para sempre. O tempo máximo de bloqueio é controlado por max.block.ms.
Isso nos dá dois resultados possíveis:
- dados suficientes são enviados e confirmados, liberando espaço no buffer,
- nenhum espaço fica disponível antes do timeout, e o envio falha.
O simulador acompanha o buffer do produtor diretamente. Você pode ver a memória sendo alocada quando registros são adicionados e liberada conforme as requisições são concluídas.
Isso é especialmente útil quando combinado com um broker lento ou uma quota de vazão. Em vez de tratar o backpressure do produtor como uma questão abstrata de configuração, você pode observar o buffer encher e ver exatamente qual envio fica bloqueado.
Retentativas e o problema das duplicatas
Suponha que o produtor envie um batch a um broker.
O broker escreve o batch com sucesso, mas a confirmação é perdida antes de chegar ao produtor.
Do ponto de vista do produtor, a requisição falhou. Ele não tem como saber se o broker escreveu os dados. Tentar novamente é, portanto, razoável.
No entanto, se a primeira requisição foi escrita com sucesso, a retentativa pode acrescentar o mesmo batch novamente.
Esse é o problema das duplicatas causado por falhas ambíguas.
O cenário de retentativa mostra ambas as requisições:
- a requisição original chega ao broker,
- a confirmação é perdida,
- o produtor tenta o batch novamente,
- o broker recebe os mesmos registros pela segunda vez.
Sem idempotência, ambas as cópias podem ser acrescentadas.
Semântica do produtor idempotente
Um produtor idempotente permite que o Kafka reconheça batches retransmitidos.
O Kafka atribui uma identidade ao produtor e rastreia números de sequência de forma independente para cada partição. Quando um batch é retransmitido com um número de sequência que o broker já processou, o broker pode confirmá-lo sem acrescentar os registros novamente.
A distinção importante é entre uma retentativa e uma duplicata genuína.
Dois registros podem conter exatamente a mesma chave e o mesmo valor e ainda assim serem dois registros separados enviados intencionalmente pela aplicação. O Kafka não deve remover esses registros.
A idempotência deduplica retentativas do protocolo do produtor. Ela não deduplica os dados da aplicação com base em seu conteúdo.
Os novos cenários mostram:
- IDs de produtor,
- números de sequência por partição,
- retentativas idempotentes bem-sucedidas,
- registros duplicados genuínos,
- detecção de sequência fora de ordem.
Por que max.in.flight.requests.per.connection importa?
Um produtor pode ter várias requisições aguardando respostas ao mesmo tempo.
Isso melhora a vazão, mas também cria um possível problema de ordenação.
Suponha que o produtor envie o batch A e depois o batch B. O batch A falha temporariamente, enquanto o batch B tem sucesso. Se A for retransmitido depois que B já foi escrito, os registros podem ser acrescentados em uma ordem diferente.
A idempotência usa números de sequência para detectar ordenação inválida.
O simulador mostra isso explicitamente. Você pode ver o número de sequência esperado pelo broker, o número carregado pelo batch que chega e o ponto em que o broker rejeita uma requisição fora de ordem.
Inspecionando o estado interno do produtor
Os novos cenários se apoiam fortemente no inspetor do simulador, que expõe o estado interno de produtores e partições, incluindo:
- contadores de retentativa,
- números de sequência do produtor,
- números de sequência do broker,
- alocação de buffer.
Normalmente, esses valores ficam escondidos por trás de métricas do produtor, logs e comportamento de protocolo. No simulador, eles podem ser inspecionados diretamente enquanto você avança ou retrocede em um cenário.
Correções no modelo do produtor
Adicionar os novos cenários também exigiu tornar o modelo de produtor existente mais preciso.
As retentativas são contadas por batch
O Kafka retransmite requisições de produção que contêm batches de registros.
Uma requisição que falha deve, portanto, consumir uma retentativa para o batch completo, não uma retentativa para cada registro dentro dele.
O simulador agora segue esse modelo. Cada registro de um batch que falhou é enviado novamente, enquanto o contador de retentativas avança uma única vez.
A memória do buffer é liberada com precisão
A memória do buffer do produtor agora é liberada conforme as confirmações chegam.
Isso corrige casos em que o simulador podia reportar backpressure mesmo que registros confirmados já devessem ter liberado sua memória.
O mesmo se aplica aos timeouts de max.block.ms. Quando um envio bloqueado expira, sua alocação de buffer é limpa.
acks=all é rastreado por partição
A durabilidade do Kafka é definida de forma independente para cada partição.
Um produtor que escreve em várias partições precisa, portanto, esperar que a partição correta satisfaça seus requisitos de confirmação. O progresso em outra partição não deve confirmar uma requisição não relacionada.
O simulador agora rastreia esse estado separadamente para cada partição e tópico.
O reset do produtor limpa o estado de idempotência
O broker armazena o estado de deduplicação associado a um produtor idempotente.
Quando um produtor simulado é removido ou resetado, esse estado também precisa ser removido. Caso contrário, um produtor recém-criado pode ter seus registros classificados incorretamente como retentativas antigas.
O comportamento de reset agora limpa o estado correspondente no lado do broker.
Retroceder restaura o estado de retentativa
Os cenários podem ser avançados e retrocedidos um passo de cada vez.
Retroceder agora reconstrói os contadores de retentativa usados durante cada rodada de simulação. O estado visível do produtor é, portanto, consistente, independentemente de o cenário estar avançando ou retrocedendo.
Melhorias no playground
A versão também contém várias correções fora do modelo do produtor.
Rebalanceamento de grupos de consumidores
Os grupos de consumidores no playground receberam várias correções de corretude:
- o sticky assignor agora minimiza a movimentação de partições em vez de reatribuir tudo do zero,
- um membro estático que fica ausente por mais tempo que
session.timeout.msé removido como qualquer outro membro, - alterar a contagem de partições de um tópico agora dispara um rebalanceamento adequado sem perturbar o estado dos membros,
- partições nunca são atribuídas a um membro que não está mais vivo.
Configuração de tópicos e compactação
O Kafka nunca reduz a contagem de partições de um tópico. Encolher apagaria dados e quebraria o particionamento baseado em chave, então kafka-topics --alter rejeita a operação com InvalidPartitionsException.
O playground agora segue a mesma regra. Uma redução na contagem de partições é rejeitada com um erro didático em vez de descartar partições silenciosamente.
Tópicos compactados também se comportam com mais precisão:
- um tombstone sobrevive até que sua própria janela de carência de
delete.retention.msexpire, - um registro sem chave não pode ser produzido em um tópico compactado,
- com
compact,delete, a retenção por tempo agora recupera registros antigos independentemente da compactação.
Os links de compartilhamento preservam o cenário completo
O playground serializa suas ações na URL para que um cenário possa ser compartilhado com outra pessoa.
O codec agora inclui todas as mudanças de configuração, incluindo atualizações na contagem de partições. Um link que solicita um tipo de cluster de um pacote de lançamento posterior agora recorre a uma topologia suportada em vez de renderizar um cluster inutilizável.
Abrir um link compartilhado deve, portanto, recriar o mesmo cenário que foi originalmente construído.
Correções de interação
Vários problemas menores do playground também foram corrigidos:
- identificadores de entidade não colidem mais depois que uma entidade é removida e recriada,
- o posicionamento de produtores funciona corretamente em grades de broker mais largas.
Cabeçalhos de segurança
O site agora envia uma Content Security Policy e uma Permissions Policy.
Por que começar pelo produtor?
As garantias do Kafka costumam ser discutidas do ponto de vista do broker: replicação, participação no ISR, high watermarks e failover de líder.
No entanto, a garantia observada por uma aplicação também depende do produtor.
Qual modo de confirmação ele usa? Ele tenta novamente? A idempotência está ativada? Quantas requisições podem estar em voo? O que acontece quando seu buffer local fica cheio?
Essas configurações determinam se uma escrita é rápida, durável, ordenada, duplicada, bloqueada ou rejeitada.
É por isso que o primeiro pacote maior de cenários do Kafka Simulator foca no produtor.
Uma vez que batching, confirmações, retentativas e idempotência ficam visíveis, os cenários posteriores que cobrem replicação, durabilidade e transações têm uma base mais clara.
Abra o Kafka Simulator, selecione a trilha do produtor e experimente os cenários de falha passo a passo.