Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

04 - Streaming de Dados (Kafka, Flink)

#data-lake#flink#kafka#schema-registry#streaming
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Streaming de Dados com Kafka e Flink

Este caso acompanha uma plataforma de mobilidade urbana com 8 milhões de corridas por mês, em que a telemetria de cada viagem chega a cada 10 segundos e se soma aos eventos de aplicação, como solicitação, match, pagamento e avaliação. O volume combinado é de 65 milhões de eventos por dia, com média de 750 eventos por segundo e pico de 9.000 na sexta à noite com chuva. Os objetivos arquiteturais são quatro: ingestão durável sem perda, agregações em janela com latência inferior a 10 segundos, exatidão financeira na cobrança por viagem e um data lake que sirva BI e machine learning sem nova coleta. O desenho escolhe Kafka como espinha dorsal de ingestão e Flink como motor de processamento, com contratos versionados no schema registry. A exatidão da cobrança é o requisito que condiciona todas as outras decisões.

Requisitos e pressupostos

O contrato de serviço do pipeline define três classes de latência e uma de exatidão:

  • Volume: 65 milhões de eventos por dia, média de 750 por segundo, pico de 9.000, tamanho médio de 1,2 KB por evento.
  • Alertas operacionais em menos de 10 segundos do fato à notificação, porque o tempo de resposta da operação de rua depende disso.
  • Dashboards de operação em menos de 1 minuto, e faturamento por viagem em menos de 5 minutos, com nenhum valor duplicado ou omitido.
  • Ordenação garantida dentro da mesma viagem e do mesmo motorista, e sem garantia entre entidades distintas.
  • Contratos versionados no schema registry, com evolução sem quebrar consumidores em produção.
  • Retenção quente de 7 dias no Kafka e histórico completo no lake, com custo de armazenamento decrescendo com a idade.
  • Privacidade por desenho: eventos não carregam identificadores diretos de pessoa; a correlação usa pseudônimo com tabela de resolução isolada, e o lake nunca recebe o mapa completo.
  • Observabilidade do próprio pipeline no mesmo padrão dos serviços, para que a plataforma não dependa de acesso manual aos brokers.

Os pressupostos simplificam o desenho. Os eventos são imutáveis e apendáveis, e nenhuma correção reescreve o passado: retificação é evento novo que se sobrepõe ao anterior. Os produtores citam o schema por identificador, então a validação acontece na borda, e não no consumidor. O faturamento tolera segundos de atraso, mas não tolera erro, o que separa este fluxo dos dashboards, que aceitam aproximação. A ordem entre entidades distintas não é garantida e nenhuma agregação a exige, o que libera o particionamento para seguir a chave de entidade.

Os eventos dividem-se em duas classes com tratamentos distintos. Telemetria é densa, apendável e tolerante a amostragem, com 60% do volume; eventos de negócio são esparsos, carregam valores financeiros e exigem exatidão total. O particionamento separa as duas classes em tópicos distintos desde o início, porque a política de retenção e o custo por byte divergem por ordem de grandeza.

Arquitetura proposta

Os produtores são o aplicativo do passageiro, o SDK de telemetria do motorista e os serviços internos, todos publicando em tópicos por domínio: corridas.telemetria, corridas.eventos e pagamentos.eventos. Os tópicos são particionados por chave de entidade, e um job Flink consome, deduplica, enriquece e agrega, publicando em três destinos: um tópico de saída para alertas, o data lake em Iceberg sobre S3 e um banco analítico para consulta operacional. O schema registry governa os contratos em Avro com compatibilidade backward. A tabela resume os componentes e as configurações que carregam a carga.

Componente Papel Configuração-chave Motivo
Produtores Publicam com schema por id acks=all, idempotência habilitada Durabilidade sem duplicação na origem
Tópicos Particionados por entidade 24 partições por tópico Ordenação por viagem e paralelismo
Schema Registry Contratos versionados Compatibilidade BACKWARD Evolução sem quebrar consumidor
Flink Dedupe, janela e agregação Checkpoint a cada 30 s Exactly-once com tolerância a falha
Banco analítico Consulta operacional Escrita idempotente por checkpoint Painel em segundos sem tocar na origem
Data lake Histórico e ML Commit por checkpoint no Iceberg Evolução de schema sem reescrita
Tópico de saída Alertas para operação Chave por região Consumo direto pelos times de plantão

O cluster Kafka opera em modo KRaft, com 3 brokers, fator de replicação 3 e min.insync.replicas 2, e todo produtor usa acks=all com idempotência habilitada. Essa combinação troca um pouco de latência por garantia de durabilidade, e a medição mostrou custo de 4 ms no p99, aceitável para um pipeline que cobra dinheiro.

Três regras de governança atravessam a arquitetura:

  • Nenhum produtor publica sem schema registrado e aprovado no pull request do contrato.
  • Nenhum consumidor lê tópico de outro domínio diretamente; a integração é por tópico de saída publicado pelo dono.
  • Toda reexecução e todo backfill passam por namespace separado, com commit idempotente para não duplicar no lake.

Um exemplo tipifica o caminho de ponta a ponta. A telemetria do motorista publica posição a cada 10 segundos em corridas.telemetria com a chave tripId; o Flink agrega distância e tempo na janela da viagem; o agregado vira registro no Iceberg e linha no analítico; e o evento de término dispara o faturamento em pagamentos.eventos, que segue o mesmo caminho com exatidão reforçada. Nenhum dashboard toca nos tópicos de origem, e nenhum serviço de negócio lê o lake diretamente.

Fluxo principal: da telemetria ao agregado

O fluxo completo tem cinco estágios verificáveis:

  • Produção com validação de schema na borda e confirmação de durabilidade.
  • Ingestão com ordenação por chave e retenção quente de 7 dias.
  • Processamento com dedupe, enriquecimento e janela por tempo de evento.
  • Publicação com commit idempotente por checkpoint nos três destinos.
  • Consumo com leitura exclusiva por domínio e alerta de lag por subtarefa.

O particionamento por tripId é a decisão estrutural: os eventos da mesma viagem caem na mesma partição, o Kafka garante ordem dentro da partição, e o Flink pode agregar sem reordenar globalmente. O job abre janelas de 5 minutos por viagem com watermark de 2 minutos para tolerar atraso do GPS, e a sessão da viagem fecha quando o evento de término chega. O resultado agregado alimenta o faturamento, e cada commit no sink carrega a versão do checkpoint, o que torna a reexecução idempotente.

# Tópico de telemetria: ordenação por viagem com paralelismo controlado
topic: corridas.telemetria
partitions: 24                    # paralelismo máximo de consumo
replication.factor: 3             # sobrevive a um broker fora
min.insync.replicas: 2            # acks=all exige duas réplicas em sync
retention.ms: 604800000           # 7 dias quentes antes do lake
cleanup.policy: delete
// Job Flink: dedupe, janela e publicação com exatidão de checkpoint
var eventos = env.fromSource(kafkaSource, WatermarkStrategy
        .<EventoCorrida>forBoundedOutOfOrderness(Duration.ofMinutes(2))
        .withTimestampAssigner((e, ts) -> e.ocorridoEm()),
    "corridas-telemetria");

var faturamento = eventos
    .keyBy(EventoCorrida::getTripId)              // ordenação por viagem
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .allowedLateness(Time.minutes(2))             // aceita atraso do GPS
    .aggregate(new AgregaDistancia(), new JanelaFaturamento())
    .sinkTo(icebergSink);                         // commit idempotente por checkpoint

env.enableCheckpointing(30_000, CheckpointingMode.EXACTLY_ONCE);

O dedupe acontece antes da janela, por chave de evento com estado TTL de 24 horas, porque o SDK do motorista pode repetir lote em reconexão. A versão do registro no Iceberg carrega o identificador do checkpoint, então uma falha e reexecução do job reescreve a mesma versão, e não uma nova. O banco analítico recebe o mesmo fluxo com upsert pela chave de viagem, e o painel de operação lê daí, nunca do Kafka diretamente.

Dados atrasados além da tolerância não são descartados: entram como retificação na viagem afetada, com flag de atraso e recomputação da janela. Na medição, 0,8% dos eventos chegam depois da watermark, e 0,05% além da latência permitida; os dois caminhos têm destino definido, o que elimina a perda silenciosa que o desenho anterior acumulava.

O caminho dos alertas é o mais rápido e o mais simples. O job publica eventos de exceção, como viagem parada por mais de 10 minutos ou desvio de rota acima do limiar, no tópico corridas.alertas com chave por região, e o consumidor do plantão empurra para o canal da região em menos de 10 segundos do fato. A regra é uma por tipo de alerta, configurável sem redeploy, e o falso positivo é tratado como defeito de regra, e não como ruído a ignorar.

Decisões e trade-offs

Decisão Alternativa descartada Trade-off aceito
Avro com schema registry JSON livre no tópico Rigidez de contrato; detecção de quebra em tempo de deploy
Exactly-once com checkpoints At-least-once com dedupe no sink Custo de checkpoint a cada 30 s; billing sem dedupe manual
Watermark de 2 minutos Processamento por tempo de chegada Alertas até 2 min mais tardios; atraso do GPS vira dado correto
Iceberg no lake Tabelas diretas no analítico Armazenamento duplicado; consultas pesadas saem do operacional
Particionamento por tripId Partição por round-robin Viagens longas concentram estado; paralelismo previsível
Estado em RocksDB Estado em heap Latência de acesso maior; escala para dezenas de GB por instância

A expressão exactly-once merece honestidade técnica. A garantia real cobre o caminho do checkpoint até os sinks transacionais; a borda de entrada é at-least-once, e o dedupe por chave no job compensa a diferença. Essa combinação entrega o efeito prático de cobrança sem duplicação com custo menor que exatidão end-to-end literal, e o time registrou a fronteira da garantia na documentação do contrato.

A escolha do Avro se sustenta em números. O payload em Avro ficou 40% menor que o JSON equivalente no mesmo volume, o que reduziu o custo de rede e de armazenamento quente na mesma proporção, e o schema registry permite ao consumidor novo validar o contrato antes do deploy. O custo é a necessidade de tooling: bibliotecas de serialização em cada linguagem e disciplina de compatibilidade que exige revisão em pull request.

O número de partições merece registro próprio. Vinte e quatro não vem de fórmula: cobre o paralelismo máximo do job com folga, e cada partição extra aumenta arquivos abertos nos brokers e overhead de gerência no Flink. O teto prático observado pela equipe de plataforma é de 4.000 partições por cluster antes da gerência de metadados pesar, e o desenho guarda distância confortável desse limite.

Escalabilidade e falhas

O lag do consumidor é a métrica central do pipeline, medida por tópico e por subtarefa, com alerta quando ultrapassa 60 segundos. O rescale do Flink segue o lag em modo reativo: o paralelismo sobe de 24 para 48 no pico de sexta, e volta com escala de descida conservadora, porque rebalancear custa estado em trânsito. O backpressure é auto-regulado pelo próprio Flink, e a pressão aparece no painel como tempo de checkpoint esticado, que é o primeiro sintoma antes do lag subir.

As falhas têm caminho previsto. Um broker fora não interrompe nada com fator de replicação 3 e duas réplicas em sync, e a recuperação dele é monitorada por taxa de re-replicação para não competir com o tráfego. Eventos inválidos, cerca de 0,3% do volume, saem por side output para uma DLQ com inspetor semanal, e nunca param o pipeline. A evolução de contrato segue regra de duas fases: campo novo entra com valor padrão, consumidores migram, e a remoção só acontece quando nenhum consumidor ativo cita o campo, verificado no registry.

Métrica Limiar de alerta Primeira ação
Lag por subtarefa 60 s Aumentar paralelismo
Duração de checkpoint 12 s Redistribuir estado
Taxa de DLQ 1% do volume Inspecionar produtor
Partições sub-replicadas 1 Verificar broker e disco
Disco dos brokers 75% Expandir ou drenar partição

O estado do job é a peça mais sensível da escala. O backend RocksDB guarda o dedupe de 24 horas e as janelas abertas, cerca de 18 GB por instância no pico, e o checkpoint serializa esse estado para o S3 a cada 30 segundos. Quando o checkpoint passou de 12 segundos de duração, o time distribuiu o estado por chave e aumentou o paralelismo antes que o lag aparecesse, porque checkpoint lento é o sintoma que antecede a falha.

Backfill é operação de rotina na plataforma. Recálculos de faturamento por correção de regra rodam em namespace separado com paralelismo dedicado, e a versão idempotente do sink garante que o resultado substitui, e não soma. O maior backfill do ano reprocessou 90 dias de telemetria em 5 horas, fora do pico, sem afetar o fluxo ao vivo.

O dimensionamento dos brokers segue o disco, e não a CPU. O cluster carrega 9 TB de retenção quente replicada, com discos NVMe e taxa de escrita média de 90 MB por segundo, e a CPU fica abaixo de 30% mesmo no pico, então qualquer alerta de broker nasce de disco ou de rede, e nunca de processamento.

A expansão do pipeline já foi ensaiada. Dobrar partições de 24 para 48 exige chaveamento novo e reprocessamento do lake, então a capacidade ociosa por partição é guardada de propósito: o tópico opera com pico de 400 eventos por segundo por partição, muito abaixo do limite, e a folga absorve o crescimento por dois anos sem re-partição.

Lições aplicáveis

  • A chave de partição é decisão estrutural: ordenação por entidade vale mais que balanceamento perfeito, e mudar chave depois custa reprocessamento completo.
  • Exactly-once efetivo é meta alcançável: checkpoints com sinks transacionais e dedupe na borda entregam cobrança sem duplicação por fração do custo.
  • Lag é o termômetro do pipeline: alerta por subtarefa com limiar em segundos pega o problema antes que o consumidor perceba.
  • Schema registry previne incidente silencioso: a quebra de contrato passa a falhar no deploy, e não às 3 da manhã no consumidor de produção.
  • Duplicar destino é mais barato que duplicar carga: o lake em Iceberg absorve as consultas pesadas, e o banco analítico permanece pequeno e rápido.
  • Guarde folga nas partições: re-partição de tópico em produção é operação de alto risco, e a capacidade ociosa é seguro barato.
  • Checkpoint lento é sintoma, e não incômodo: esticar além do intervalo é o aviso que antecede lag e falha, e a resposta é redistribuir estado.
  • Separe telemetria de evento de negócio: retenção, custo e exatidão divergem por ordem de grandeza, e tópicos distintos evitam compensações ruins.
  • Documente a fronteira da garantia: exatidão declarada sem o alcance definido gerou uma semana de debate com o time financeiro que a usa.