Na Parte 1, cobrimos vários tópicos-chave. Recomendo que você leia, pois esta próxima parte se baseia nela.
Como uma revisão rápida, na parte 1, consideramos nossos dados sob uma perspectiva ampla e diferenciamos entre dados internos e dados externos. Também discutimos esquemas e contratos de dados e como eles fornecem os meios para negociar, alterar e evoluir nossos fluxos ao longo do tempo. Finalmente, cobrimos os tipos de eventos Fato (Estado) e Delta. Eventos de Fato são os melhores para comunicar estado e desacoplar sistemas, enquanto eventos de Delta tendem a ser usados mais para dados internos, como em sourcing de eventos e outros casos de uso fortemente acoplados.
Tabelas Normalizadas Criam Fluxos Normalizados
Tabelas normalizadas levam a fluxos de eventos normalizados. Conectores (por exemplo, CDC) extraem dados diretamente do banco de dados para um conjunto espelhado de fluxos de eventos. Isso não é ideal, pois cria um acoplamento forte entre as tabelas internas do banco de dados e os fluxos de eventos externos.
Considere um simples e-commerce Item e suas tabelas associadas Marca e Status Tributário.
As tabelas Marca e Status Tributário se relacionam com a tabela Item através de relações de chave estrangeira. Embora mostremos apenas um item na tabela, você provavelmente terá milhares (ou milhões), dependendo dos produtos que vende.
É comum configurar um conector para cada tabela, extrair os dados da tabela, compor em eventos e escrever cada tabela em um fluxo de eventos dedicado.
Expor as tabelas subjacentes no banco de dados leva a um fluxo de eventos correspondente por tabela. Embora seja fácil começar dessa forma, isso leva a vários problemas, que podem ser resumidos como um problema de acoplamento problema ou um problema de custo problema. Vamos examinar cada um.
Problema: Consumidores se Acoplam ao Modelo Interno
Expor a tabela de origem Item como está força os consumidores a se acoplarem diretamente a ela. Mudanças no modelo de dados do sistema de origem afetarão os consumidores downstream.
Digamos que refatoramos a tabela Item para extrair Precificação em sua própria tabela.
Reestruturar as tabelas de origem resulta em um contrato de dados quebrado para o fluxo de itens. O consumidor não recebe mais os mesmos dados de item que originalmente esperava. Também precisamos criar um novo conector — um novo Price stream — e, finalmente, reestruturar nossa lógica de consumidor para que ele volte a funcionar. Renomear colunas, alterar valores padrão e mudar tipos de coluna são outras formas de mudanças quebradas introduzidas pelo acoplamento rígido no modelo de dados interno.
Problema: Junções de Streaming São (Normalmente) Caras
Bancos de dados relacionais são projetados para resolver junções rapidamente e com baixo custo. Junções de streaming, infelizmente, não são.
Considere dois serviços que desejam acessar um Item, seu Imposto e suas informações de Marca. Se os dados já estiverem escritos em seu fluxo correspondente, então cada consumidor (à direita na imagem abaixo) terá que calcular as mesmas junções para denormalizar Item, Marca e Imposto.
Essa estratégia pode incorrer em altos custos, tanto em horas-dev para escrever os aplicativos quanto em custos de servidor para computar os joins. Resolver joins de streaming em grande escala pode resultar em muito rearranjo de dados, o que acarreta custos de poder de processamento, rede e armazenamento. Além disso, nem todas as frameworks de processamento de stream suportam joins, especialmente em chaves estrangeiras. Dessas que o fazem, como Flink, Spark, KSQL ou Kafka Streams (por exemplo), você se verá limitado a um subconjunto de linguagens de programação (Java, Scala, Python).
Solução: Servir Dados Desnormalizados É o Melhor
Como princípio, torne as correntes de eventos fáceis para seus consumidores usarem. Desnormalize os dados antes de torná-los disponíveis para os consumidores usando uma camada de abstração e crie um modelo externo explícito contrato de dados (dados no exterior) para os consumidores se acoplarem.
As mudanças no modelo interno permanecem isoladas nos sistemas de origem. Os consumidores recebem um contrato de dados bem definido para acoplamento. Mudanças feitas no modelo de origem podem prosseguir sem obstáculos, desde que o sistema de origem mantenha o contrato de dados para os consumidores.
Mas onde desnormalizamos? Duas opções:
- Reconstruir fora do sistema de origem por meio de um serviço de junção específico.
- Durante a criação do evento no sistema de origem usando o padrão Transactional Outbox.
Vamos dar uma olhada em cada solução.
Opção 1: Desnormalizar Usando um Serviço de Junção Específico
Neste exemplo, os fluxos à esquerda espelham as tabelas de onde vieram.
Nós unimos os eventos usando um aplicativo desenvolvido para esse fim (ou consulta SQL em tempo real) baseado nas relações de chave estrangeira e emitimos um único fluxo de itens enriquecido.
Logicamente, estamos resolvendo as relações e compactando os dados em uma única linha desnormalizada.

Os unificadores desenvolvidos para esse fim dependem de frameworks de processamento de fluxo, como Apache Kafka Streams e Apache Flink, para resolver tanto junções de chave primária quanto de chave estrangeira. Eles materializam os dados do fluxo em formatos de tabela interna duráveis, permitindo que o aplicativo unificador una eventos em qualquer período – não apenas aqueles limitados por uma janela de tempo.
Os unificadores usando Flink ou Kafka Streams também são notavelmente escaláveis — eles podem escalar para cima e para baixo conforme a carga e lidar com volumes massivos de tráfego.
Dica: Não coloque nenhuma lógica de negócios no unificador. Para ter sucesso neste padrão, os dados unidos devem representar accuramente a fonte, simplesmente como um resultado desnormalizado. Deixe os consumidores downstream aplicarem sua própria lógica de negócios, usando os dados desnormalizados como uma única fonte de verdade.
Se você não quiser usar um unificador downstream, há outras opções. Vamos dar uma olhada no padrão de caixa de saída transacional a seguir.
Opção 2: Padrão de Caixa de Saída Transacional
Primeiro, crie uma tabela de caixa de saída dedicada para escrever eventos no fluxo.
Primeiro, envolva todas as atualizações necessárias na tabela interna dentro de uma transação. As transações garantem que quaisquer atualizações feitas na tabela interna também serão gravadas na tabela de saída.
A tabela de saída permite isolar o modelo de dados interno, pois você pode unir e transformar os dados antes de gravá-los na sua tabela de saída. A tabela de saída atua como a camada de abstração entre seus dados internos e os dados externos, funcionando como um contrato de dados para seus consumidores.
Finalmente, você pode usar um conector para obter os dados da tabela de saída e inseri-los no Kafka.
Você deve garantir que a tabela de saída não cresça indefinidamente — exclua os dados após serem capturados pelo CDC ou periodicamente com um trabalho agendado.
Exemplo: Desnormalizando Eventos de Rastreamento de Comportamento do Usuário
Rastrear o comportamento do usuário em suas páginas da web e aplicativos é uma fonte comum de eventos normalizados — pense no Google Analytics ou em opções de primeira parte internas. Mas não incluímos todas as informações no evento; em vez disso, limitamo-nos a identificadores (mais rápidos, menores, mais baratos), desnormalizando após a criação dos fatos.
Considere um fluxo de eventos de cliques em itens detalhando quando um usuário clicou em um item enquanto navegava por itens de comércio eletrônico. Note que esse evento de clique no item não contém informações mais ricas sobre o item, como nome, preço e descrição, apenas ids
básicos.
A primeira coisa que muitos consumidores de fluxo de cliques fazem é juntá-lo ao fluxo de fatos do item. E, como você está lidando com muitos eventos de clique, descobre que isso acaba usando uma grande quantidade de recursos de computação. Uma aplicação Flink construída com um propósito específico pode juntar os cliques no item com os dados detalhados do item e emiti-los para um fluxo de cliques no item enriquecido.
Empresas maiores com várias divisões (e sistemas) provavelmente verão seus dados provenientes de diferentes fontes, e juntar após o fato usando um unificador de fluxo é o resultado mais provável.
Considerações sobre Dimensões que Mudam Lentamente
Já discutimos as considerações de desempenho de escrever eventos contendo grandes conjuntos de dados (por exemplo, grandes blobs de texto) e domínios de dados que mudam frequentemente (por exemplo, inventário de itens). Agora, vamos olhar para dimensões que mudam lentamente (SCDs), frequentemente indicadas por uma relação de chave estrangeira, pois estas podem ser outra fonte de volumes significativos de dados.
Vamos voltar ao nosso exemplo de item novamente. Digamos que você tenha uma operação que atualiza a Tabela de Itens. Vamos renomear o item de Bigorna para Bigorna de Ferro.
Ao atualizar os dados no banco de dados, também emitimos o item atualizado (digamos, via padrão de saída), completo com o status fiscal desnormalizado e a tabela de marca.
No entanto, também precisamos considerar o que acontece quando alteramos valores nas tabelas de marca ou imposto. Atualizar uma dessas dimensões que mudam lentamente pode resultar em um número significativamente grande de atualizações para todos os itens afetados.
Por exemplo, a empresa ACME passa por um rebranding e vem com um novo nome de marca, mudando de ACME para Rotunda. Produzimos outro evento para ItemId=123
.
No entanto, Rotunda (antiga ACME) provavelmente tem muitas centenas (ou milhares) de itens que também são atualizados por essa mudança, resultando em um número correspondente de eventos de itens enriquecidos atualizados.
Ao desnormalizar SCDs e relações de chave estrangeira, tenha em mente o impacto que uma mudança no SCD pode ter no fluxo de eventos como um todo. Você pode decidir abrir mão da desnormalização e deixá-la por conta do consumidor no caso de a mudança no SCD resultar em milhões ou bilhões de eventos atualizados.
Resumo
A desnormalização facilita o uso de dados pelos consumidores, mas vem à custa de mais processamento upstream e uma seleção cuidadosa dos dados a incluir. Os consumidores podem ter mais facilidade para construir aplicações e podem escolher entre uma ampla gama de tecnologias, incluindo aquelas que não suportam nativamente junções de streaming.
Normalizar dados upstream funciona bem quando os dados são pequenos e atualizados com pouca frequência. Tamanhos maiores de eventos, atualizações frequentes e SCDs são todos fatores a serem observados ao determinar o que desnormalizar upstream e o que deixar para seus consumidores fazerem por conta própria.
Finalmente, escolher quais dados incluir em um evento e o que deixar de fora é um equilíbrio entre as necessidades dos consumidores, as capacidades dos produtores e as relações únicas do modelo de dados. Mas o melhor lugar para começar é entendendo as necessidades dos seus consumidores e a isolação do modelo de dados interno do seu sistema de origem.
Source:
https://dzone.com/articles/how-to-design-event-streams-part-2