Implementação de um pipeline de dados de streaming
Trustly teve um grande crescimento nos últimos anos, o que afetou a forma como fazemos dados e análises. Nossos desafios provavelmente não são únicos e ainda não sabemos se as escolhas que fizemos foram as corretas. Só o tempo dirá. Estou na empresa há quase seis anos e vi nossos recursos dedicados de dados e análises crescerem de zero para talvez 40 pessoas, dependendo de como você nos contar. Daqui a um ano, esse número terá crescido ainda mais.
Histórico
Originalmente, eu pretendia que esta postagem do blog fosse bastante técnica e se concentrasse apenas em alguns desafios explícitos de engenharia que enfrentamos e como os resolvemos. Mas logo percebi que não faria muito sentido se eu não descrevesse um pouco mais o contexto. Uma coisa que notei ao fazer parte de uma organização maior é que se torna ainda mais importante manter o foco nos problemas que você está tentando resolver. Como engenheiro, sinto-me melhor quando posso passar um dia inteiro tentando resolver um problema técnico interessante (e provavelmente não sou o único). Como ser humano, tenho a tendência de gravitar em torno de coisas que me fazem sentir bem (não é mesmo?). Infelizmente, o que me faz sentir bem nem sempre é o que é mais valioso para meu empregador. Em uma organização pequena, onde todo mundo conhece todo mundo e todos os funcionários almoçam juntos às segundas-feiras, esse desalinhamento rapidamente se torna óbvio e pode ser resolvido, mas quanto maior a organização se torna, mais tempo posso continuar resolvendo problemas que talvez não fossem tão importantes no início. É mais difícil, tanto para mim quanto para os outros, falsificar minha crença de que estou trabalhando em algo realmente importante. Especialmente quando parece algo que um engenheiro de dados deveria estar fazendo, como criar um pipeline de dados de streaming.
Vamos primeiro dar uma olhada em uma das nossas plataformas de dados em lote existentes, que usa o Google Cloud Platform. Temos várias fontes de dados, a mais importante das quais é o nosso produto de pagamento, mas também inclui sistemas de CRM, sistemas financeiros, Jira, etc. A maior parte dos dados é extraída desses sistemas usando tarefas escritas em Python e acionadas pelo Airflow. Nós ingerimos todos esses dados e os colocamos em um formato bastante bruto no BigQuery, nosso lago de dados, se preferir. As transformações posteriores são escritas em SQL e executadas pelo dbt. Há alguns anos, começamos a orquestrar todas essas transformações SQL com operadores do Airflow, mas mudamos tudo para o dbt há cerca de um ano e não nos arrependemos nem um pouco. O dbt funciona muito bem e nos permite dissociar a transformação de dados da ingestão.
Ao passar de uma organização pequena para uma maior, é preciso começar a pensar em processos de desacoplamento e em como dimensionar suas equipes. Quando toda a organização de dados é composta por apenas três pessoas, todos saberão um pouco de tudo, e isso não é problema. Se você quiser aumentar essa escala em 10 vezes, a melhor maneira de fazer isso provavelmente não será exigir que cada novo membro da equipe saiba Java, Python, SQL e arquitetura de nuvem, além de ter um entendimento detalhado de quais métricas de produto são importantes, em qual sistema de origem encontrar os dados brutos, como defini-los e como trabalhar com as partes interessadas do negócio. As pessoas que sabem tudo isso (ou que podem aprender) são um recurso escasso. Em vez disso, permitir que os analistas e engenheiros analíticos desenvolvam e mantenham pipelines em SQL, sem ter que se preocupar com a forma como os dados brutos chegam à plataforma em primeiro lugar - um trabalho que é melhor deixar para os engenheiros de dados - é algo que descobrimos que nos permite remover gargalos e distribuir as cargas de trabalho entre muitas equipes.
Por que streaming?
Então, voltando ao streaming, por que precisamos dele? A resposta óbvia seria que precisamos de dados em tempo real e, embora isso seja certamente algo pelo qual devemos nos esforçar, eu não diria que é o que mais importa para nós no momento. Se todo o resto for igual, ter dados, ou seja, saber algo, mais cedo é melhor do que mais tarde. Mas todo o resto não é igual quando se trata de batch versus streaming. Eu preferiria classificar nossas prioridades atuais da seguinte forma:
- Capturar mudanças
- Reduzir o tempo de carregamento
- Diminuir a latência
Aqui estou pensando em reduzir a latência de um dia para uma hora (em vez de um segundo para um milissegundo). Vamos dar uma olhada em cada uma delas.
Capturar mudanças
Em grande parte, a análise consiste em encontrar padrões subjacentes que o ajudarão a entender o mundo. Entender o mundo ao seu redor o ajuda a tomar decisões - esperamos que sejam decisões que ajudem sua empresa a crescer. Há muito a ser dito sobre como (e como não) transformar dados em insights (ou, melhor ainda, em decisões), e deixo esses desafios para nossos analistas e cientistas de dados. Dito isso, acho que é bastante incontroverso afirmar que o tempo é uma dimensão importante em grande parte do trabalho de análise. Tempo como em: "Ontem vendemos por 5 coroas suecas, hoje vendemos por 10 coroas suecas, por quanto venderemos amanhã?"
Jay Kreps escreveu uma postagem clássica em seu blog com o título "The Log: O que todo engenheiro de software deve saber sobre a abstração unificadora de dados em tempo real", que explica muito melhor do que eu as vantagens de capturar mudanças ao longo do tempo no formato de um registro de eventos. A conclusão para o nosso caso de uso é que o nosso sistema de pagamentos geralmente armazena informações sobre o mundo como ele é agora, e apenas em um grau limitado como ele era ontem. Ou meio segundo atrás. Seria impraticável usar um banco de dados OLTP para manter todas as alterações por longos períodos de tempo, mas se o sistema de pagamentos puder simplesmente disparar eventos à medida que eles acontecem e alguém os coletar do outro lado de um corretor de mensagens, eles poderão ser usados para "reproduzir" e reconstruir o estado do mundo em qualquer momento que você queira estudar em retrospectiva.
Reduzir o tempo de carregamento
Infelizmente, o banco de dados do nosso produto de pagamento não tem um registro de data e hora de "última modificação" para todas as tabelas. Isso significa que não há como saber quais linhas de uma tabela foram alteradas entre ontem e hoje, ou entre um segundo atrás e agora. Para ter dados consistentes na plataforma de dados, precisamos exportar tabelas inteiras do sistema de produção todas as noites. E como os volumes de transações do Trustlyexplodiram, o mesmo aconteceu com nossas exportações de dados noturnas. Você poderia argumentar que o banco de dados deveria ter sido projetado de uma maneira melhor para começar, mas quando o sistema foi criado há muitos anos, o foco era obter um produto que funcionasse e que pudéssemos vender, e não a adaptação a uma plataforma de análise que poderíamos construir vários anos no futuro (se ainda estivéssemos no mercado naquela época).
Obter um fluxo de eventos, ou seja, apenas as novidades, reduz drasticamente a computação e a largura de banda gastas na atualização dos dados em nossa plataforma para o estado mais recente.
Diminuir a latência
Provavelmente, essa é a primeira coisa que você pensa quando ouve a palavra "streaming" e, é claro, é algo que também é importante para nós. O fato de não estar limitado a ter dados atualizados apenas uma vez por dia permite novos usos dos dados. O que você deve ter em mente, porém, é que o streaming é difícil. Uma comparação no mundo real poderia ser a diferença entre fazer um trabalho de encanamento nas linhas de abastecimento de água (streaming) e regar as plantas (lote). As consequências de um erro no primeiro caso são muito mais graves (casa inundada) do que no segundo (peitoril da janela molhado). Se você tiver uma estufa, o trabalho de encanamento para instalar a água lá pode valer a pena, mas se você tiver apenas cinco plantas em seu apartamento, talvez seja melhor restringir suas ambições de estar na vanguarda tecnológica. Eu diria que o mesmo pensamento vale para as plataformas de dados.
Nossa solução
Com isso em mente, há cerca de um ano, decidimos criar uma estrutura que melhorasse a ingestão de dados em nossa plataforma de dados. O trabalho não está de forma alguma concluído (provavelmente nunca estará), mas isto é o que conseguimos até o momento.
Produtores -> Pub/Sub -> Beam (fluxo de dados) -> Google Cloud Storage -> Airflow (Cloud Composer) -> BigQuery -> dbt -> BigQuery -> Consumidores
Essa solução está em produção para alguns subcomponentes do nosso sistema de pagamento desde o final de agosto, e ainda estamos avaliando como melhorá-la. Fizemos algumas observações até o momento.
Esquemas rígidos são essenciais para dados estruturados
Optamos pelo Avro para a codificação das mensagens enviadas pelos produtores. Depois de experimentar os recursos de detecção automática de JSON puro do BigQuery no ano passado, sabíamos que precisávamos de algo mais rigoroso para não acabar com um inferno de falhas/manutenção para a equipe de dados. Juntamente com uma das equipes de desenvolvimento de produtos (e eventuais produtores de dados), analisamos o Protobuf e o JSON com esquemas, mas o Avro parecia ser a opção com menos desvantagens.
Embora haja algum suporte rudimentar para esquemas no GCP, por exemplo, você pode atribuir esquemas Avro a tópicos Pub/Sub, nossa experiência mostra que isso é muito mais imaturo do que, por exemplo, o Kafka tem a oferecer. O GCP está melhorando o tempo todo, portanto, talvez daqui a um ano as coisas sejam diferentes. Por enquanto, temos um armazenamento de esquemas em um bucket do GCS onde os produtores colocam seus esquemas e de onde o trabalho de ingestão do Beam pode lê-los.
Use serviços de nuvem quando possível
A menos que você tenha necessidades muito específicas ou já tenha muita competência em alguma área, descobrimos que o uso de serviços em nuvem é uma maneira fácil de colocar algo escalável em produção no menor tempo possível. Na verdade, começamos a construir o pipeline com base no Kafka, mas depois mudamos para o Pub/Sub quando ficou evidente que a equipe de serviços de dados teria que fazer uma boa parte da operação e manutenção do componente de entrega de eventos (Kafka ou Pub/Sub). Um motivo para não optar por um serviço em nuvem é o risco de aprisionamento, mas se você tomar alguns cuidados ao fazer a implementação, por exemplo, usar a estrutura apenas para sua finalidade principal de uma forma desacoplada e não começar a (ab)usá-la para todos os tipos de coisas, ela deverá ser substituível. Melhor ainda, é claro, se o serviço de nuvem for baseado em uma estrutura de código aberto em que a lógica possa ser transferida para a solução hospedada de outro fornecedor de nuvem, por exemplo, Airflow (Cloud Composer) ou Beam (Dataflow), caso seja necessário.
A API Python do Beam é imatura em comparação com o Java
A competência da nossa equipe é principalmente em Python e SQL, portanto, uma escolha natural para nós foi desenvolver o código Beam que faz a ingestão do Pub/Sub para o BigQuery em Python. No entanto, depois de passar um bom tempo, começamos a perceber que o uso de Java teria nos dado melhor suporte e mais opções. Para sermos justos, a documentação do Beam não esconde esse fato, mas nós, talvez de forma um pouco ingênua, não levamos isso em consideração antes. Por exemplo, a falta de um sistema de tipagem rigoroso no Python pode tornar mais rápido o início de um trabalho, mas quando você quer ter certeza de que pode lidar com todas as conversões de tipos entre um esquema Avro e uma tabela BigQuery, o Java é mais confiável.
Também é verdade que as fontes e os coletores Java fornecem mais funcionalidades prontas para uso, por exemplo, você pode fornecer ao conector BigQueryIO em Java um nome de tabela para gravar com base em uma função que você define para ser avaliada em tempo de execução. Para Python, o nome da tabela deve ser baseado em um campo no evento de entrada. Conclusão: se você optar pelo Python, terá mais restrições sobre como criar seu pipeline e alguns dos recursos que ele oferece parecem mais ou menos experimentais. Normalmente, é necessário examinar o código-fonte para descobrir o que ele realmente faz e quais são as limitações existentes.
Considere o microbatching para problemas em que você não precisa de tempo real
Em Trustly, temos casos de uso que exigem dados de streaming quase em tempo real para o pipeline, mas nenhum deles está totalmente implementado na plataforma ainda. Um dos motivos pelos quais escolhemos o Beam para ingestão é que ele nos permitiria combinar fluxos de streaming e em lote e, até certo ponto, alternar entre os dois. Dependendo das necessidades do consumidor de dados, poderíamos oferecer um "produto de dados" que não fosse mais complexo do que o necessário. No nosso caso, micro batching significa armazenar dados como arquivos Avro no GCS a cada 10 minutos. Uma vez por dia, carregamos esses arquivos em tabelas particionadas no BigQuery. Durante o dia, os dados podem ser consultados (com latência de até 10 minutos) a partir de uma tabela externa do BigQuery que aponta para a pasta no GCS onde os arquivos de hoje estão localizados; muito parecido com as camadas de lote e velocidade de uma arquitetura lambda.
A vantagem disso é que temos mais liberdade para estruturar os dados no BigQuery. Se os dados estiverem sendo transmitidos constantemente para as tabelas, será difícil alterar as coisas, por exemplo, adicionar particionamento a uma tabela. (Se voltarmos à analogia do encanamento, é como tentar substituir a mangueira da máquina de lavar louça quando ela está funcionando e não há como desligar o fornecimento de água). Além disso, como estamos ingerindo dias inteiros em tabelas particionadas por dia, obtemos trabalhos idempotentes e, no caso de uma falha em algum ponto do pipeline, é muito mais fácil resolver as coisas e voltar a um estado conhecido.
Conclusão
Você merece algum crédito se chegou até aqui, mas sinto que apenas arranhei a superfície de (um componente de) nossa configuração de dados. Há mais coisas que eu gostaria de compartilhar. Por exemplo, nossa jornada com o Airflow/Cloud Composer e como ampliamos o uso do dbt na organização. No entanto, espero que isso tenha, pelo menos, dado uma ideia do que estamos fazendo e os motivos pelos quais escolhemos nossa solução específica. Se você acha que há maneiras melhores de fazer isso ou se simplesmente acha que essas tarefas parecem ser um desafio interessante, por que não se juntar a nós? Estamos sempre à procura de engenheiros de dados qualificados para aumentar a equipe e melhorar nossa prática dentro da empresa.
Per Isacson
Chefe de engenharia de dados