Por Rita Sodt e Igor Maravić (Spotify)
com Gary Luo, Gary O’Connor e Kate Ward
O processamento de dados é um campo complexo que está constantemente evoluindo para atender às demandas de conjuntos de dados cada vez maiores, transformações intensivas de dados e o desejo por resultados rápidos, confiáveis e econômicos. O cenário atual apresenta conjuntos de dados que são gerados e coletados de uma variedade de fontes – desde estatísticas de uso móvel até redes de sensores integrados, logs de aplicativos da web e muito mais. Os pipelines de processamento de dados podem transformar esses conjuntos de dados frequentemente ilimitados, desordenados e de escala global em armazenamento estruturado e indexado que pode ajudar a informar decisões comerciais cruciais ou desbloquear novos recursos de produtos. Além de fornecer insights sobre o comportamento do sistema e do usuário, o processamento de dados muitas vezes é crucial para os negócios. Dados atrasados ou incorretos em seu pipeline podem se manifestar em problemas enfrentados pelo usuário que são caros, intensivos em mão de obra e demorados para corrigir.
Este capítulo começa usando exemplos de produtos para examinar alguns tipos comuns de aplicações de pipelines de processamento de big data. Em seguida, exploramos como identificar os requisitos e padrões de design do pipeline, e enumeramos algumas melhores práticas para gerenciar pipelines de processamento de dados ao longo do ciclo de desenvolvimento. Cobrimos compensações que você pode fazer para otimizar seu pipeline e técnicas para medir os sinais importantes da saúde do pipeline. Para um serviço permanecer saudável e confiável após ser implantado, os Engenheiros de Confiabilidade de Site (SREs), assim como os desenvolvedores, devem ser capazes de navegar por todas essas tarefas. Idealmente, os SREs deveriam estar envolvidos nesse trabalho desde seus estágios iniciais: as equipes de SRE do Google regularmente consultam equipes que estão desenvolvendo um pipeline de processamento de dados para garantir que o pipeline possa ser facilmente lançado, modificado e executado sem causar problemas para os clientes.
Por fim, o estudo de caso do Spotify fornece uma visão geral de seu pipeline de processamento de entrega de eventos, que utiliza uma combinação de soluções internas, Google Cloud e outras soluções de terceiros para gerenciar um pipeline de processamento de dados complexo e crucial para o negócio. Se você é proprietário de um pipeline diretamente, ou possui outro serviço que depende dos dados que um pipeline produz, esperamos que você possa usar as informações deste capítulo para ajudar a tornar seus pipelines (e serviços) mais confiáveis.
Para uma discussão abrangente das filosofias do Google sobre pipelines de processamento de dados, consulte o Capítulo 25 do primeiro livro de SRE.
Aplicações de pipelines
Existem uma ampla variedade de aplicações de pipelines, cada uma com suas próprias vantagens e casos de uso. Um pipeline pode envolver múltiplos estágios; cada estágio é um processo separado com dependências em outros estágios. Um pipeline pode conter vários estágios, que são abstraídos com uma especificação de alto nível. Um exemplo disso está no Cloud Dataflow: um usuário escreve a lógica de negócios com uma API relativamente de alto nível, e a tecnologia de pipeline traduz esses dados em uma série de etapas ou estágios onde a saída de um é a entrada de outro. Para lhe dar uma ideia da amplitude das aplicações de pipelines, a seguir descrevemos várias aplicações de pipelines e seus usos recomendados. Usamos dois exemplos de empresas com requisitos diferentes de pipeline e implementação para demonstrar diferentes maneiras de atender às suas respectivas necessidades de dados. Esses exemplos ilustram como seu caso de uso específico define os objetivos do seu projeto e como você pode usar esses objetivos para tomar uma decisão informada sobre qual pipeline de dados funciona melhor para você.
Processamento de Eventos/Transformação de Dados para Ordenar ou Estruturar Dados
O modelo Extract Transform Load (ETL) é um paradigma comum no processamento de dados: os dados são extraídos de uma fonte, transformados e possivelmente desnormalizados, e então “recarregados” em um formato especializado. Em aplicações mais modernas, isso pode se assemelhar a um processo cognitivo: aquisição de dados de algum tipo de sensor (ao vivo ou reprodução) e uma fase de seleção e marshalling, seguida pelo “treinamento” de uma estrutura de dados especializada (como uma rede de aprendizado de máquina).
Os pipelines ETL funcionam de forma semelhante. Os dados são extraídos de uma única (ou múltiplas) fontes, transformados e então carregados (ou escritos) em outra fonte de dados. A fase de transformação pode servir a uma variedade de casos de uso, tais como:
- Fazendo alterações no formato dos dados para adicionar ou remover um campo
- Funções de computação agregadas em várias fontes de dados
- Aplicando um índice aos dados para que tenham características melhores para servir trabalhos que consomem os dados
Análise de Dados
Inteligência empresarial se refere a tecnologias, ferramentas e práticas para coletar, integrar, analisar e apresentar grandes volumes de informações para possibilitar uma tomada de decisão melhor. Seja você um produto de varejo, um jogo para dispositivos móveis ou sensores conectados à Internet das Coisas, agregar dados de vários usuários ou dispositivos pode ajudá-lo a identificar onde as coisas estão quebradas ou funcionando bem.
Para ilustrar o caso de uso da análise de dados, vamos examinar uma empresa fictícia e seu jogo para dispositivos móveis e web recentemente lançado, chamado “Shave the Yak”. Os proprietários querem saber como seus usuários interagem com o jogo, tanto em seus dispositivos móveis quanto na web. Como primeiro passo, eles produzem um relatório de análise de dados do jogo que processa dados sobre eventos dos jogadores. Os líderes de negócios da empresa solicitaram relatórios mensais sobre os recursos mais usados do jogo, para que possam planejar o desenvolvimento de novos recursos e realizar análises de mercado. As análises móveis e da web para o jogo são armazenadas em tabelas do Google Cloud BigQuery que são atualizadas três vezes ao dia pelo Google Analytics. A equipe configurou um trabalho que é executado sempre que novos dados são adicionados a essas tabelas. Ao ser concluído, o trabalho faz uma entrada na tabela de agregação diária da empresa.
Machine Learning
As aplicações de aprendizado de máquina (Machine Learning ou ML) são usadas para uma variedade de propósitos, como ajudar a prever câncer, classificar spam e personalizar recomendações de produtos para usuários. Tipicamente, um sistema de ML possui as seguintes etapas:
- As características dos dados e suas etiquetas são extraídas de um conjunto de dados maior.
- Um algoritmo de ML treina um modelo com as características extraídas.
- O modelo é avaliado em um conjunto de dados de teste.
- O modelo é disponibilizado (servido) para outros serviços.
- Outros sistemas tomam decisões usando as respostas servidas pelo modelo.
Para demonstrar um pipeline de ML em ação, vamos considerar um exemplo de uma empresa fictícia, Dressy, que vende vestidos online. A empresa deseja aumentar sua receita oferecendo recomendações direcionadas aos seus usuários. Quando um novo produto é carregado no site, a Dressy deseja que seu sistema comece a incorporar esse produto nas recomendações aos usuários dentro de 12 horas. Em última análise, a Dressy gostaria de apresentar aos usuários recomendações quase em tempo real conforme eles interagem com o site e avaliam os vestidos. Como primeiro passo em seu sistema de recomendação, a Dressy investiga as seguintes abordagens:
Colaborativo
Mostrar produtos que são similares entre si.
Agrupamento (Clustering)
Mostrar produtos que foram gostados por um usuário semelhante.
Baseado em conteúdo
Mostrar produtos que são similares a outros produtos que o usuário visualizou ou gostou.
Como uma loja online, a Dressy possui um conjunto de dados de informações de perfil de usuário e avaliações, então eles optam por usar um filtro de agrupamento (clustering). Novos produtos que são carregados em seu sistema não possuem dados estruturados ou rótulos consistentes (por exemplo, alguns fornecedores podem incluir informações extras sobre cor, tamanho e características do vestido usando diferentes categorias e formatos). Consequentemente, eles precisam implementar um pipeline para pré-processar os dados em um formato compatível com o TensorFlow que combina tanto as informações do produto quanto os dados do perfil do usuário. O sistema de ML inclui pipelines para pré-processar dados de várias fontes necessárias para treinar o modelo. Usando os dados de treinamento, a equipe de desenvolvimento da Dressy cria um modelo TensorFlow para servir as recomendações apropriadas aos clientes. A Figura 13-1 mostra a solução de ML completa. Detalhamos cada etapa a seguir.
Figura 13-1. Pipeline de processamento de dados de ML
- A equipe de desenvolvimento opta por usar um pipeline de fluxo de dados em tempo real, o Google Cloud Dataflow, para pré-processar os dados em um formato de vestidos rotulados, enviando imagens para um serviço de classificação de imagem que retorna uma lista de características.
- A equipe pré-processa dados de várias fontes que serão usados para treinar um modelo que retorna os cinco vestidos mais semelhantes. Seu fluxo de trabalho gera um modelo de ML a partir dos dados de produtos de vestuário e do histórico de compras dos perfis de clientes armazenados no BigQuery.
- Eles optam por usar um pipeline de fluxo de dados em tempo real para pré-processar os dados em um formato de perfis de personalização de clientes. Esses perfis são usados como entrada para treinar um modelo TensorFlow. O binário do modelo TensorFlow treinado é armazenado em um bucket do Google Cloud Storage (GCS). Antes de ser promovido para produção, a equipe garante que o modelo passe por verificações de precisão ao ser avaliado em relação a um conjunto de teste dos dados pré-processados usados para a avaliação do modelo.
- Um serviço fornece as recomendações para um cliente específico, que são usadas pelos frontends da web e móveis. A equipe utiliza o TensorFlow com o serviço de previsão online Cloud ML.
- Um serviço de frontend voltado para o cliente para fazer compras serve os dados do usuário com base nas recomendações de vestidos atualizadas do serviço de previsão.
A Dressy percebeu que ocasionalmente um novo modelo não é publicado por mais de 24 horas, e as recomendações geram erros intermitentes. Esse é um problema comum quando um novo modelo é implantado pela primeira vez; no entanto, existem algumas etapas simples que você pode tomar para resolver esse problema. Se você começar a notar que decisões, classificações ou recomendações não estão sendo apresentadas ou estão desatualizadas ou incorretas, pergunte a si mesmo:
- Os dados estão presos entrando no pipeline antes de poderem ser pré-processados para treinar o modelo?
- Temos um modelo de ML ruim causado por um bug de software? Há muito spam? As características usadas para treinar o modelo são mal escolhidas?
- Uma nova versão do modelo de ML foi gerada recentemente, ou uma versão obsoleta do modelo está em produção?
Felizmente, a Dressy possui um conjunto de ferramentas para monitorar e detectar um problema antes que seus clientes experimentem qualquer problema. Se e quando ocorrer uma interrupção, essas ferramentas podem ajudá-los a reparar rapidamente ou reverter qualquer código problemático. Para obter mais detalhes sobre a implementação de monitoramento e alerta, consulte o Capítulo 4.
Melhores práticas de pipeline
As seguintes melhores práticas de pipeline se aplicam a pipelines que são executados como um serviço (ou seja, pipelines que são responsáveis por processar corretamente os dados de forma oportuna para consumo por outros sistemas). Múltiplos passos são necessários para implantar adequadamente um pipeline como um serviço. Esses passos variam desde a definição e medição das necessidades do cliente com um SLO, até responder graciosamente às degradações e falhas, escrever documentação e criar um ciclo de vida de desenvolvimento que identifica problemas antes que eles alcancem a produção.
Definir e medir Objetivos de Nível de Serviço (SLOs)
É importante detectar automaticamente quando seu pipeline não está saudável e se você está falhando em atender às necessidades do seu cliente. Receber notificações quando você está em perigo de exceder seu orçamento de erro (para mais detalhes sobre orçamento de erro, consulte o Capítulo 2) ajuda a minimizar o impacto no cliente. Ao mesmo tempo, é importante encontrar um equilíbrio confortável entre confiabilidade e lançamentos de recursos – seus clientes se importam com ambos. O restante desta seção fornece exemplos de SLOs de pipeline e como mantê-los.
Atualidade dos dados
A maioria dos SLOs de atualidade dos dados em pipelines estão em um dos seguintes formatos:
- X% dos dados processados em Y [segundos, dias, minutos].
- Os dados mais antigos não têm mais do que Y [segundos, dias, minutos].
- O job do pipeline foi concluído com sucesso dentro de Y [segundos, dias, minutos].
Por exemplo, o jogo móvel Shave the Yak poderia escolher um SLO que exige que 99% de todas as ações do usuário que impactam a pontuação do usuário sejam refletidas no placar dentro de 30 minutos.
Correção dos dados
Criar SLOs para a correção dos dados garante que você seja alertado sobre possíveis erros nos dados do seu pipeline. Por exemplo, um erro de correção em um pipeline de faturamento poderia resultar em clientes sendo cobrados em excesso ou em falta. Um objetivo de correção pode ser difícil de medir, especialmente se não houver uma saída correta predefinida. Se você não tiver acesso a esses dados, pode gerá-los. Por exemplo, use contas de teste para calcular a saída esperada. Uma vez que você tenha esses “dados de ouro”, pode comparar a saída esperada e real. A partir daí, você pode criar monitoramento para erros/discrepâncias e implementar alertas baseados em limite à medida que os dados de teste fluem através de um sistema de produção real.
Outro SLO de correção de dados envolve análise retrospectiva. Por exemplo, você pode definir um objetivo de que não mais do que 0,1% das suas faturas estejam incorretas por trimestre. Você pode definir outro objetivo de SLO para o número de horas/dias que dados ruins ou erros são servidos a partir dos dados de saída do pipeline. A noção de correção dos dados varia de acordo com o produto e aplicação.
Isolamento de dados/balanceamento de carga
Às vezes, você terá segmentos de dados que são de maior prioridade ou que exigem mais recursos para processamento. Se você prometer um SLO mais rigoroso em dados de alta prioridade, é importante saber que esses dados serão processados antes dos dados de menor prioridade se seus recursos se tornarem limitados. A implementação desse suporte varia de pipeline para pipeline, mas frequentemente se manifesta como diferentes filas em sistemas baseados em tarefas ou diferentes trabalhos. Os trabalhadores do pipeline podem ser configurados para executar a tarefa de maior prioridade disponível. Pode haver várias aplicações de pipeline ou trabalhos de trabalhadores de pipeline em execução com diferentes configurações de recursos – como memória, CPU ou camadas de rede – e o trabalho que não consegue ser concluído com êxito em trabalhadores com provisionamento mais baixo pode ser reprocessado em trabalhadores com provisionamento mais alto. Em tempos de limitações de recursos ou do sistema, quando é impossível processar todos os dados rapidamente, essa separação permite processar preferencialmente itens de alta prioridade em relação aos de menor prioridade.
Medição de ponta a ponta
Se o seu pipeline possui uma série de estágios, pode ser tentador medir um SLO por estágio ou por componente. No entanto, medir SLOs dessa forma não captura a experiência do cliente ou a saúde do sistema de ponta a ponta. Por exemplo, imagine que você tenha um pipeline baseado em eventos como o Google Analytics. O SLO de ponta a ponta inclui a coleta de entrada de log e qualquer número de etapas do pipeline que ocorram antes que os dados alcancem o estado de serviço. Você poderia monitorar cada estágio individualmente e oferecer um SLO para cada um, mas os clientes se preocupam apenas com o SLO para a soma de todos os estágios. Se você estiver medindo SLOs para cada estágio, seria forçado a apertar seus alertas por componente, o que poderia resultar em mais alertas que não refletem a experiência do usuário.
Além disso, se você medir a correção dos dados apenas por estágio, poderá perder bugs de corrupção de dados de ponta a ponta. Por exemplo, cada estágio em seu pipeline pode relatar que está tudo bem, mas um estágio introduz um campo que espera que um trabalho a jusante processe. Este estágio anterior assume que os dados extras foram processados e usados para atender às solicitações dos usuários. Um trabalho a jusante não espera o campo adicional, então ele descarta os dados. Ambos os trabalhos pensam que estão corretos, mas o usuário não vê os dados.
Planeje para falhas de dependência
Uma vez que você defina seu SLO, é uma boa prática confirmar que você não está dependendo demais dos SLOs/SLAs de outros produtos que não conseguem cumprir seus compromissos. Muitos produtos, como o Google Cloud Platform, listam suas promessas de SLA em seus sites. Uma vez identificadas quaisquer dependências de terceiros, no mínimo, projete para a maior falha considerada em seus SLAs divulgados. Por exemplo, ao definir um SLO, o proprietário de um pipeline que lê ou escreve dados no Cloud Storage garantiria que os tempos de atividade e garantias anunciadas sejam apropriados. Se a garantia de tempo de atividade em uma única região fosse menor do que a exigida pelo pipeline para cumprir seu SLO no tempo de processamento de dados, o proprietário do pipeline poderia optar por replicar os dados entre regiões para obter uma disponibilidade mais alta.
Quando a infraestrutura de um provedor de serviços viola seus SLAs, o resultado pode impactar negativamente pipelines dependentes. Se seu pipeline depende de garantias mais rigorosas do que o provedor de serviços anuncia, seu serviço pode falhar mesmo que o provedor de serviços permaneça dentro de seu SLA. Às vezes, planejar realisticamente para falhas de dependência pode significar aceitar um nível mais baixo de confiabilidade e oferecer um SLA menos restrito para seus clientes.
No Google, para incentivar o desenvolvimento de pipelines levando em consideração falhas de dependência, planejamos paralisações planejadas. Por exemplo, muitos pipelines no Google dependem da disponibilidade do datacenter onde são executados. Nossos Testes de Recuperação de Desastres (DiRT) frequentemente visam esses sistemas, simulando uma interrupção regional. Quando ocorre uma interrupção regional, os pipelines que planejaram para a falha são automaticamente redirecionados para outra região. Outros pipelines são atrasados até que o operador do pipeline com falha seja alertado por seu monitoramento e faça o redirecionamento manualmente. O redirecionamento manual bem-sucedido pressupõe que o pipeline pode obter recursos suficientes para iniciar uma pilha de produção em outra região. No melhor cenário, um redirecionamento manual mal sucedido prolonga uma interrupção. No pior cenário, os jobs de processamento podem ter continuado a processar dados obsoletos, o que introduz dados desatualizados ou incorretos em quaisquer pipelines subsequentes. As táticas de recuperação de um incidente como este variam dependendo da sua configuração. Por exemplo, se dados corretos foram sobrescritos com dados incorretos, você pode ter que restaurar os dados de uma versão de backup anterior e reprocessar quaisquer dados ausentes.
Em resumo, é uma boa prática se preparar para o dia em que os sistemas dos quais você depende estejam indisponíveis. Mesmo os melhores produtos podem falhar e experimentar interrupções. Pratique regularmente cenários de recuperação de desastres para garantir que seus sistemas sejam resilientes a falhas comuns e incomuns. Avalie suas dependências e automatize as respostas do seu sistema o máximo possível.
Criar e manter documentação do pipeline
Quando bem escrita e mantida, a documentação do sistema pode ajudar os engenheiros a visualizar o pipeline de dados e suas dependências, entender tarefas complexas do sistema e potencialmente reduzir o tempo de inatividade em uma interrupção. Recomendamos três categorias de documentação para o seu pipeline.
Diagramas do sistema
Diagramas do sistema, semelhantes à Figura 13-2, podem ajudar os engenheiros de plantão a encontrar rapidamente pontos potenciais de falha. No Google, incentivamos as equipes a desenhar diagramas de sistema que mostrem cada componente (tanto as aplicações do pipeline quanto os repositórios de dados) e as transformações que ocorrem em cada etapa. Cada um dos componentes e transformações mostrados em seu diagrama pode ficar preso, fazendo com que os dados parem de fluir pelo sistema. Cada componente também pode introduzir um bug de configuração de software ou aplicativo que impacta a correção dos dados.
Um diagrama do sistema deve conter links rápidos para outras informações de monitoramento e depuração em diferentes estágios do pipeline. Idealmente, esses links devem extrair informações de monitoramento ao vivo, exibindo o status atual de cada estágio (por exemplo, esperando o término do trabalho dependente/processando/completo). Exibir informações históricas de tempo de execução também pode indicar se um estágio do pipeline está demorando mais do que o esperado. Esse atraso pode antecipar uma degradação de desempenho ou uma interrupção.
Finalmente, mesmo em sistemas complexos, um diagrama do sistema facilita para os desenvolvedores analisar as dependências de dados das quais devem estar cientes durante os lançamentos de novos recursos.
Figura 13-2. Diagrama do sistema de pipeline (PII = informações pessoalmente identificáveis)
Documentação de processo
É importante documentar como realizar tarefas comuns, como lançar uma nova versão de um pipeline ou introduzir uma alteração no formato dos dados. Idealmente, você também deve documentar tarefas menos comuns (frequentemente manuais), como inicializar um serviço ou desligar um serviço em uma nova região. Uma vez que suas tarefas estejam documentadas, investigue a possibilidade de automatizar qualquer trabalho manual. Se as tarefas e o sistema forem automatizados, considere gerar sua documentação diretamente da fonte para mantê-las sincronizadas.
Entradas de manual
Cada condição de alerta em seu sistema deve ter uma entrada correspondente no manual que descreve as etapas para a recuperação. No Google, achamos útil vincular essa documentação em todas as mensagens de alerta enviadas aos engenheiros de plantão. As entradas do manual são discutidas com mais detalhes no Capítulo 11 do nosso primeiro livro.
Mapeie o ciclo de vida do desenvolvimento
Como mostrado na Figura 13-3, o ciclo de vida do desenvolvimento de um pipeline (ou de uma mudança em um pipeline) não é muito diferente do ciclo de vida do desenvolvimento de outros sistemas. Esta seção segue um fluxo de lançamento típico através de cada estágio do ciclo de vida do desenvolvimento do pipeline.
Figura 13-3. Ciclo de vida do desenvolvimento do pipeline com fluxo de lançamento
Prototipagem
A primeira fase do desenvolvimento envolve a prototipagem do seu pipeline e a verificação de semântica. A prototipagem garante que você possa expressar a lógica de negócios necessária para executar seu pipeline. Você pode descobrir que uma linguagem de programação permite expressar melhor sua lógica de negócios, ou que uma linguagem específica se integra mais facilmente com suas bibliotecas existentes. Um modelo de programação específico pode se adequar ao seu caso de uso específico (por exemplo, Dataflow versus MapReduce, lotes versus streaming). Para um exemplo de comparação de modelos de programação concluída, consulte nosso post no blog “Dataflow/Beam & Spark: Uma Comparação de Modelos de Programação”. Se você estiver adicionando um recurso a um pipeline existente, recomendamos adicionar seu código e executar testes unitários na fase de prototipagem.
Testando com uma execução de 1% em seco
Depois de concluir seu protótipo, é útil executar uma configuração pequena em toda a pilha usando dados de produção. Por exemplo, execute seu pipeline usando um conjunto experimental ou uma execução de 1% dos dados de produção em um ambiente não de produção. Aumente gradualmente a escala, acompanhando o desempenho do seu pipeline para garantir que você não encontre nenhum gargalo. Quando seu produto for lançado para os clientes, execute testes de desempenho. Esses testes são uma etapa de desenvolvimento integral que ajuda a prevenir interrupções causadas pelos lançamentos de novos recursos.
Staging
Antes de implantar na produção, é útil executar seu sistema em um ambiente de pré-produção (ou staging). Os dados em seu ambiente de staging devem ser o mais próximo possível dos dados de produção reais. Recomendamos manter uma cópia completa dos dados de produção ou pelo menos um subconjunto representativo. Testes unitários não capturarão todos os problemas do pipeline, portanto, é importante deixar os dados fluírem pelo sistema de ponta a ponta para capturar problemas de integração.
Testes de integração também podem identificar erros. Usando tanto testes unitários quanto de integração, execute uma comparação A/B dos seus dados recém-gerados com dados conhecidos anteriormente como bons. Por exemplo, verifique sua versão anterior em busca de diferenças esperadas ou inesperadas antes de certificar o lançamento e marcá-lo como pronto para mover para a produção.
Canarização
Testar e verificar pipelines requer mais tempo e cuidado do que trabalhos sem estado – os dados são persistidos e as transformações frequentemente são complexas. Se algo der errado na sua versão de produção, é importante detectar o problema cedo para limitar o impacto. A canarização do seu pipeline pode ajudar! A canarização é um processo no qual você implanta parcialmente seu serviço (neste caso, a aplicação do pipeline) e monitora os resultados. Para uma discussão mais detalhada sobre a canarização, consulte o Capítulo 16. A canarização está vinculada a todo o pipeline em vez de a um único processo. Durante uma fase de canarização, você pode optar por processar os mesmos dados de produção reais que o pipeline ao vivo, mas pular as gravações no armazenamento de produção; técnicas como mutação de duas fases podem ajudar (consulte a seção “Mutação Idempotente e de Duas Fases”). Muitas vezes, você terá que esperar o ciclo completo de processamento terminar antes de descobrir quaisquer problemas que afetem os clientes. Após sua execução em seco (ou mutação de duas fases), compare os resultados do seu pipeline de canarização com o seu pipeline ao vivo para confirmar sua saúde e verificar diferenças de dados.
Às vezes é possível avançar através da canarização gradualmente atualizando tarefas de um trabalho ou atualizando primeiro em uma região e depois em outra, mas nem sempre isso é possível com pipelines. Pipelines que usam dados replicados, como Dataproc e Dataflow, suportam pontos de extremidade regionais e impedem esse tipo de progressão de canário – você não pode recarregar uma célula isoladamente de outra. Se você executar um pipeline multihomed, pode não ser possível implantar em uma única região (ou uma porcentagem de servidores) como você faria com um trabalho de serviço. Em vez disso, realize um rollout para uma pequena porcentagem de dados primeiro, ou como descrito anteriormente, faça um rollout primeiro em modo de execução em seco.
Durante a verificação do seu ambiente de canarização ou pré-produção, é importante avaliar a saúde do seu pipeline. Normalmente, você pode usar as mesmas métricas que usa para acompanhar seus SLOs. Verificar seu canário é uma tarefa que se presta bem à automação.
Realizando uma implantação parcial
Além de canarizar suas alterações, você também pode querer realizar uma implantação parcial, especialmente se houver um lançamento ou alteração importante de recurso que possa impactar o desempenho do sistema e o uso de recursos. Pode ser difícil prever o impacto desses tipos de lançamentos sem primeiro testar suas alterações em um subconjunto de tráfego real. Você pode implementar uma implantação parcial como uma opção de sinalização ou configuração em seu pipeline que aceita um subconjunto permitido de dados. Considere primeiro processar seus novos recursos em uma ou duas contas, e então aumentar gradualmente a quantidade de dados (por exemplo, ~1%, ~10%, ~50%, e finalmente, 100% dos seus dados de amostra).
Existem várias maneiras pelas quais sua implantação parcial pode dar errado: os dados de entrada podem estar incorretos ou atrasados, seu processamento de dados pode conter um bug, ou o armazenamento final pode ter um erro. Qualquer um desses problemas pode resultar em uma interrupção. Evite promover um conjunto de dados corrompidos para seus front-ends de baixa latência. Procure detectar esses tipos de problemas o mais cedo possível, antes que afetem seus usuários.
Implantação em produção
Depois de ter promovido totalmente seus novos binários e/ou configurações de pipeline para produção, você deve ter uma confiança razoável de que examinou quaisquer problemas potenciais (e se ocorrer um problema, que seu monitoramento o alertará). Se sua implantação der errado, seja capaz de restaurar rapidamente a partir de um estado conhecido como bom (por exemplo, reverter os binários) e marcar quaisquer dados potencialmente corrompidos como ruins (por exemplo, substituir os dados corrompidos por dados de uma versão de backup anterior, garantir que nenhum trabalho leia os dados afetados e/ou reprocesse os dados se necessário).
Reduzir a concentração e os padrões de carga de trabalho
A concentração ocorre quando um recurso é sobrecarregado devido a um acesso excessivo, resultando em uma falha na operação. Os pipelines são suscetíveis a padrões de carga de trabalho – tanto através de leituras quanto de gravações – que podem causar atrasos em regiões isoladas de dados. Alguns exemplos comuns de concentração incluem:
- Erros ocorrem porque múltiplos trabalhadores do pipeline estão acessando uma única tarefa de serviço, causando sobrecarga.
- Exaustão da CPU devido ao acesso simultâneo a um conjunto de dados disponível apenas em uma máquina. Frequentemente, os detalhes internos do armazenamento de dados têm um nível mais baixo de granularidade que pode se tornar indisponível se acessado intensivamente (por exemplo, um tablete do Spanner pode ficar sobrecarregado devido a uma seção problemática de dados, mesmo que a maior parte do armazenamento de dados esteja bem).
- Latência devido à contenção de bloqueio ao nível da linha em um banco de dados.
- Latência devido ao acesso simultâneo a um disco rígido, que excede a capacidade física da cabeça do disco de mover-se rápido o suficiente para localizar rapidamente os dados. Nesse caso, considere o uso de unidades de estado sólido.
- Uma grande unidade de trabalho que requer muitos recursos.
A concentração pode ser isolada a um subconjunto de dados. Para combater a concentração, você também pode bloquear dados de granularidade fina, como registros individuais. Se esses dados estiverem bloqueados, o restante do pipeline pode progredir. Tipicamente, sua infraestrutura pode fornecer essa funcionalidade. Se um pedaço de trabalho de processamento estiver consumindo uma quantidade desproporcional de recursos, o framework do pipeline pode rebalancear dinamicamente dividindo o trabalho em pedaços menores. Para garantir segurança, ainda é melhor incluir um desligamento de emergência na lógica do seu cliente para permitir que você pare o processamento e isole pedaços de trabalho de processamento de granularidade fina caracterizados por grande uso de recursos ou erros. Por exemplo, você deve ser capaz de definir rapidamente um sinalizador ou enviar uma configuração que permita pular dados de entrada que correspondam a um certo padrão ou usuário problemático.
Outras estratégias para reduzir a concentração incluem:
- Restruturar seus dados ou padrões de acesso para distribuir a carga uniformemente.
- Reduzir a carga (por exemplo, alocar estaticamente parte ou toda a sua carga de dados).
- Reduzir a granularidade do bloqueio para evitar a contenção do bloqueio de dados.
Implementar o escalonamento automático e o planejamento de recursos
Picos na carga de trabalho são comuns e podem levar a quedas no serviço se você não estiver preparado para eles. O escalonamento automático pode ajudá-lo a lidar com esses picos. Ao usar o escalonamento automático, você não precisa provisionar para a carga máxima 100% do tempo (para mais detalhes sobre o escalonamento automático, consulte o Capítulo 11). Manter constantemente o número de trabalhadores necessários para a capacidade máxima é um uso caro e ineficiente de recursos. O escalonamento automático desliga os trabalhadores ociosos para que você não pague por recursos desnecessários. Essa estratégia é particularmente importante para pipelines de streaming e cargas de trabalho variáveis. Pipelines em lote podem ser executados simultaneamente e consumirão tantos recursos quanto estiverem disponíveis.
Prever o crescimento futuro do seu sistema e alocar capacidade de acordo garante que seu serviço não fique sem recursos. Também é importante ponderar o custo dos recursos em relação ao esforço de engenharia necessário para tornar o pipeline mais eficiente. Ao realizar o planejamento de recursos com uma estimativa de crescimento futuro, tenha em mente que os custos podem não se limitar apenas à execução do seu trabalho de pipeline. Você também pode estar pagando pelos custos de armazenamento de dados e largura de banda de rede para replicar dados entre regiões ou para gravações e leituras entre regiões. Além disso, alguns sistemas de armazenamento de dados são mais caros do que outros. Embora os custos unitários de armazenamento sejam baixos, esses custos podem aumentar rapidamente para conjuntos de dados muito grandes ou padrões de acesso a dados caros que usam muitos recursos de computação nos servidores de armazenamento. É uma boa prática ajudar a reduzir os custos examinando periodicamente seu conjunto de dados e eliminando conteúdo não utilizado.
Embora a eficácia de uma série de estágios de pipeline deva ser medida de acordo com seu SLO de ponta a ponta, a eficiência do pipeline e o uso de recursos devem ser medidos em cada estágio individual. Por exemplo, imagine que você tenha muitos trabalhos usando o BigQuery e observe um aumento significativo no uso de recursos do BigQuery após um lançamento. Se você puder determinar rapidamente quais trabalhos são responsáveis, poderá concentrar seu esforço de engenharia nesses trabalhos para reduzir os custos.
Adote as políticas de controle de acesso e segurança
Os dados fluem através do seu sistema e frequentemente são persistidos ao longo do caminho. Ao gerenciar qualquer dado persistido, recomendamos que você adote os seguintes princípios de privacidade, segurança e integridade de dados:
- Evite armazenar informações pessoalmente identificáveis (PII) em armazenamento temporário. Se for necessário armazenar PII temporariamente, certifique-se de que os dados estejam devidamente criptografados.
- Restrinja o acesso aos dados. Conceda a cada estágio do pipeline apenas o acesso mínimo necessário para ler os dados de saída do estágio anterior.
- Defina limites de tempo de vida (TTL) para logs e PII.
Considere uma instância do BigQuery vinculada a um projeto do GCP cujas permissões de acesso podem ser gerenciadas com o Google Cloud Identity and Access Management (IAM), como o exemplo da Dressy descrito anteriormente. Criar projetos e instâncias diferentes por função permite um escopo mais refinado para restringir o acesso. As tabelas podem ter um projeto principal e criar visualizações entre projetos de clientes para permitir acesso controlado a eles. Por exemplo, a Dressy restringiu o acesso às tabelas que contêm informações confidenciais do cliente para trabalhos de funções de projeto específicas.
Requisitos e design do pipeline
O mercado atual oferece muitas opções de tecnologia e estrutura de pipeline, e pode ser avassalador identificar qual delas melhor se adequa ao seu caso de uso. Algumas plataformas fornecem pipelines totalmente gerenciados. Outras oferecem mais flexibilidade, mas exigem mais gerenciamento prático. Na Engenharia de Confiabilidade de Sistemas (SRE), frequentemente dedicamos uma quantidade significativa de tempo durante a fase de design para avaliar qual tecnologia é a melhor opção. Comparamos e contrastamos as várias opções de design com base nas necessidades do usuário, requisitos do produto e restrições do sistema. Esta seção discute ferramentas que você pode usar tanto para avaliar suas opções de tecnologia de pipeline quanto para fazer melhorias nos pipelines existentes.
Quais recursos você precisa?
A Tabela 13-1 fornece uma lista de recursos que recomendamos otimizar ao gerenciar um pipeline de processamento de dados. Alguns desses recursos podem já estar presentes em sua tecnologia de pipeline existente (por exemplo, por meio de plataformas de pipeline gerenciadas, lógica de aplicativo cliente ou ferramentas operacionais). Sua aplicação pode não precisar de alguns desses recursos, por exemplo, você não precisa de semântica “exatamente uma vez” se suas unidades de trabalho forem idempotentes e puderem ser executadas mais de uma vez para o mesmo resultado.
Tabela 13-1. Recomendação de recursos do pipeline de dados
Item do Pipeline: Latência
Recurso:
- Utilize uma API que suporte streaming, batch, ou ambos. O processamento em streaming geralmente é melhor do que o processamento em lote para suportar aplicações de baixa latência. Se você escolher batch, mas eventualmente quiser streaming, uma API que seja intercambiável pode reduzir o custo de migração posteriormente.
Item do Pipeline: Correção dos dados
Recurso:
- Semântica de exatamente-uma-vez global. Você pode exigir que os dados sejam processados (no máximo) uma vez para obter resultados corretos.
- Mutações em duas fases.
- Funções de janelamento para processamento de eventos e agregações. Você pode querer janelas de tempo fixas, de sessão ou deslizantes para dividir os dados (já que os dados nem sempre são processados na ordem em que são recebidos). Você também pode querer garantias de ordenação.
- Monitoramento de caixa-preta.
- A capacidade de controlar o fluxo de vários trabalhos ou estágios do seu pipeline. Esse controle deve permitir que você bloqueie um trabalho até que outro seja concluído, para que o trabalho não processe dados incompletos.
Item do Pipeline: Alta disponibilidade:
Recurso:
- Multihoming.
- Dimensionamento automático.
Item do Pipeline: Tempo Médio para Resolver (MTTR) incidentes em processamento de dados.
Recurso:
- Vincule suas alterações de código a um lançamento, o que permite rollback rápido.
- Tenha procedimentos de backup e restauração de dados testados em vigor.
- Em caso de interrupção, garanta que você possa drenar facilmente uma região de atendimento ou processamento.
- Tenha mensagens de alerta, painéis e logs úteis para depuração. Em particular, seu monitoramento deve identificar rapidamente o(s) motivo(s) pelo(s) qual um pipeline está atrasado e/ou por que os dados estão corrompidos. Use checkpoints de dados para ajudar a recuperar mais rapidamente quando um pipeline é interrompido.
Item do Pipeline: Tempo Médio para Detectar (MTTD) interrupções.
Recurso:
- Certifique-se de ter monitoramento de SLO em vigor. Alertas fora de SLO permitem detectar problemas que afetam seus clientes. Alertar sobre o sintoma (em vez da causa) reduz lacunas de monitoramento.
Item do Pipeline: Ciclo de vida do desenvolvimento para evitar que erros cheguem à produção
Recurso:
- Recomendamos executar qualquer alteração em um ambiente de canário antes de implantar na produção. Essa estratégia reduz a possibilidade de uma alteração impactar os SLOs na produção.
- Item do Pipeline: Inspeção e previsão de uso de recursos ou custos
Recurso:
- Crie (ou use um existente) painel de contabilidade de recursos. Certifique-se de incluir recursos como armazenamento e rede.
- Crie uma métrica que permita correlacionar ou prever o crescimento.
- Item do Pipeline: Facilidade de desenvolvimento
Recurso:
- Suporte a um idioma que melhor se adapte ao seu caso de uso. Muitas vezes, as tecnologias de pipeline limitam suas opções a um ou dois idiomas.
- Use uma API simples para definir transformações de dados e expressar a lógica do seu pipeline. Considere o equilíbrio entre simplicidade e flexibilidade.
- Reutilize bibliotecas base, métricas e relatórios. Quando você está criando um novo pipeline, recursos reutilizáveis permitem que você se concentre no desenvolvimento de qualquer nova lógica de negócios.
Item do Pipeline: Facilidade de Operação
Recurso:
- Utilize ao máximo ferramentas de automação e operacionais existentes. Fazê-lo reduz os custos operacionais, pois você não precisa manter suas próprias ferramentas.
- Automatize o máximo possível de tarefas operacionais.
- Tarefas maiores que são executadas raramente podem incluir uma cadeia de dependências e pré-requisitos que podem ser demasiadamente numerosos ou complexos para um humano avaliar em tempo hábil (por exemplo, mover seus dados e pilha de pipeline da região A para a região B e, em seguida, desligar a região A). Para facilitar uma transição como essa, considere investir em automação. Talvez introduza algumas verificações de saúde do pipeline na pilha de pipeline na região B antes de colocá-la em produção.
Mutations idempotentes e de duas fases
Os pipelines podem processar grandes volumes de dados. Quando um pipeline falha, alguns dados precisam ser reprocesados. Você pode usar o padrão de design de mutações idempotentes para evitar armazenar dados duplicados ou incorretos. Uma mutação idempotente é um tipo de mutação que pode ser aplicada várias vezes com o mesmo resultado. Implementar esse padrão de design permite que execuções separadas de um pipeline com os mesmos dados de entrada sempre produzam o mesmo resultado.
Quando testando ou realizando um canarying em um pipeline, é necessário saber se as mutações aplicadas são aceitáveis para o proprietário do pipeline de acordo com a saída esperada. O padrão de design de mutação de duas fases pode ajudar nisso. Tipicamente, os dados são lidos de uma fonte e transformados, e então uma mutação é aplicada. Com a mutação de duas fases, as próprias mutações são armazenadas em um local temporário. Uma etapa de verificação separada (ou pipeline) pode ser executada contra essas mutações potenciais para validá-las quanto à correção. Uma etapa de pipeline de acompanhamento aplica as mutações verificadas apenas após as mutações passarem pela validação. A Figura 13-4 mostra um exemplo de mutação de duas fases.
Figura 13-4. Mutação de Duas Fases
Checkpointing
Tipicamente, os pipelines são processos de longa duração que analisam ou mutam grandes quantidades de dados. Sem considerações especiais, pipelines que são terminados prematuramente perderão seu estado, exigindo que o pipeline inteiro seja executado novamente. Isso é especialmente verdadeiro para pipelines que criam modelos de IA, pois cada iteração do cálculo do modelo depende de cálculos anteriores. O checkpointing é uma técnica que permite que processos de longa duração, como pipelines, salvem periodicamente o estado parcial em armazenamento para que possam retomar o processo posteriormente.
Embora o checkpointing seja frequentemente usado para o caso de falha, também é útil quando um trabalho precisa ser preemptado ou reagendado (por exemplo, para alterar limites de CPU ou RAM). O trabalho pode ser encerrado de forma limpa e, ao ser reagendado, é capaz de detectar quais unidades de trabalho já foram processadas. O checkpointing tem a vantagem adicional de permitir que um pipeline pule leituras ou cálculos potencialmente caros porque ele já sabe que o trabalho foi concluído.
Padrões de código
Alguns padrões de código comuns podem tornar seus pipelines mais eficientes de gerenciar e reduzir o esforço necessário para fazer alterações ou atualizações.
Reutilizando código
Se você opera vários pipelines similares e deseja implementar uma nova capacidade de monitoramento ou métrica, você precisa instrumentar cada sistema separadamente. Este fluxo de trabalho comum não é difícil se você usar a estratégia certa. Implementar bibliotecas de código reutilizável permite que você adicione uma métrica de monitoramento em um único lugar e a compartilhe entre vários pipelines ou estágios. Bibliotecas compartilhadas permitem que você:
- Obtenha insights em todos os pipelines de dados de uma maneira padrão.
- Reutilize outros sistemas de análise de dados para cada pipeline (por exemplo, um relatório de tráfego que funcione para todos os seus pipelines).
- Emita alertas na mesma métrica para vários trabalhos, como um alerta genérico de frescor de dados.
Usando a abordagem de microsserviços para criar pipelines
Ao usar microsserviços, é importante ter um serviço que execute uma única tarefa e o faça bem. É mais fácil operar um grupo de microsserviços que usem as mesmas bibliotecas principais, variando apenas na lógica de negócios, do que operar muitos serviços personalizados. Um padrão semelhante pode ser aplicado a pipelines. Em vez de criar um aplicativo de pipeline monolítico, crie pipelines menores que você possa lançar e monitorar separadamente. Ao fazer isso, você obterá os mesmos benefícios que obtém de uma arquitetura de microsserviços.
Pipeline pronto para produção
Como discutido no Capítulo 18, uma PRR (Revisão de Prontidão para Produção) é o processo que as equipes de SRE (Engenharia de Confiabilidade de Site) do Google usam para integrar um novo serviço. No mesmo espírito, usamos uma matriz de maturidade de pipeline ao consultar sobre a escolha ou design de uma tecnologia de pipeline.
Matriz de maturidade do pipeline
A matriz na Tabela 13-2 mede cinco características-chave (mas você pode estender a matriz para medir outras características que deseja otimizar ou padronizar para):
- Tolerância a falhas
- Escalabilidade
- Monitoramento e depuração
- Transparência e facilidade de implementação
- Testes de unidade e integração
A matriz de maturidade representa o conhecimento coletivo de muitos especialistas em pipelines no Google. Esses indivíduos são responsáveis por executar pipelines em vários produtos do Google e operacionalizar os sistemas associados.
Cada característica é medida em uma escala de 1 a 5, onde 1 representa “Caótico” (não planejado, ad hoc, arriscado, totalmente manual) e 5 representa “Melhoria Contínua”. Para avaliar seu sistema, leia as descrições para cada característica abaixo e selecione o marco mais adequado. Se mais de um marco se aplicar, use a pontuação intermediária (ou seja, 2 ou 4). Uma planilha de pontuação concluída lhe dará uma imagem clara de onde seu sistema precisa de melhorias.
Recomendamos que você dedique tempo para fazer melhorias em quaisquer áreas fracas identificadas pela matriz. Instrumentar monitoramento, alerta e outras ferramentas recomendadas pela matriz pode levar tempo. Ao fazer melhorias, você pode começar procurando por produtos existentes ou ferramentas de código aberto que atendam às suas necessidades em vez de criar as suas próprias. No Google, incentivamos as equipes a usar tecnologias de pipeline existentes ou ferramentas que ofereçam suporte e recursos de pipeline prontos para uso. Quanto mais ferramentas e processos puderem ser reutilizados, melhor.
Tabela 13-2. Matriz de Maturidade do Pipeline, com marcos de exemplo para maturidade inicial, média e avançada
Tolerância a falhas
Failover
Chaotic: Sem suporte para failover
Funcional: Algum suporte para retries de unidades de trabalho (mesmo que manual)
Melhoria contínua: Multihomed com failover automático
Agendamento global de trabalhos
Chaotic: Sem suporte para agendamento global de trabalhos, multihoming ou failover
Funcional: Suporte para processamento hot/hot/hot (processa o mesmo trabalho em todas as três regiões, então se alguma região estiver indisponível, pelo menos uma ainda estará em execução)
Melhoria contínua: Suporte para processamento efetivo warm/warm/warm (distribui o trabalho em todas as três regiões e armazena o trabalho centralmente para lidar com a perda de qualquer região)
Gestão de tarefas falhadas
Chaotic: Sem suporte para unidades de trabalho falhadas
Funcional: –
Melhoria contínua:
-
- Retentativas automáticas para unidades de trabalho falhadas
- Quarentena automática de unidades de trabalho ruins
Escalabilidade
Escalonamento automático do pool de trabalhadores disponíveis
Chaotic: Sem escalonamento automático; intervenção manual necessária
Funcional: O escalonamento automático funciona com o uso de ferramentas manuais adicionais
Melhoria contínua: Suporte embutido para escalonamento automático com a necessidade de configuração dentro de uma ferramenta de terceiros
Repartição dinâmica automática para efeito de carga balanceada em todo pool
Chaotic: As unidades de trabalho são fixas sem capacidade de fazer alterações
Funcional: Suporta reescalonamento manual do trabalho, ou o reescalonamento pode ser realizado automaticamente com código adicional
Melhoria contínua: Suporte embutido para sub-repartição dinâmica para equilibrar o trabalho entre o pool de trabalhadores disponíveis
Derramamento de carga/priorização de tarefas
Chaotic: Nenhuma priorização de unidades de trabalho existe
Funcional: Alguma capacidade para a priorização de unidades de trabalho
Melhoria contínua:
-
- Uma funcionalidade fácil de usar para a priorização de unidades de trabalho existe Suporte embutido para o derramamento de carga
- Os trabalhadores entendem a notificação de preempção, após a qual um trabalhador limpará (concluir o trabalho/mitigar)
Monitoramento e depuração
Ferramentas e capacidades de depuração
Chaotic: Sem registros; sem maneira de identificar ou rastrear unidades de trabalho falhadas
Funcional: Existe uma solução para identificar uma unidade de trabalho falhada e extrair logs associados
Melhoria contínua:
-
- Existe uma solução que permite ao usuário acessar logs para o momento em que a unidade de trabalho falhou; esses dados são recuperados diretamente da unidade de trabalho falhada
- Existe uma solução para automaticamente colocar em quarentena e reprocessar uma unidade de trabalho falhada
Painéis e visualizações
Chaotic: Sem painéis ou soluções de visualização que suportem a exibição de informações do pipeline
Funcional: Existe um painel de fácil configuração que mostra as seguintes informações:
-
- Número de unidades de trabalho em várias etapas de conclusão
- Informações de latência e envelhecimento para cada etapa
Melhoria contínua:
-
- Uma visualização granular de todo o mapa de execução de um pipeline
- Uma visualização para atrasos até cada etapa
- Uma visualização e justificativa para estrangulamento e recuo
- Informações sobre fatores limitantes devido ao uso de recursos
- Informações sobre a distribuição de máquinas de trabalhadores de estado interno (por exemplo, gráfico de pilha) Informações sobre quantas unidades de trabalho estão falhando, presas ou lentas
- As estatísticas de execução históricas são apresentadas e preservadas
Facilidade de implementação e transparência
Descoberta
Chaotic: Sem recurso para descoberta (uma lista dos pipelines que estão em execução e seu status)
Funcional: Algum suporte para descoberta; configuração manual pode ser necessária, ou nem todos os pipelines são descobríveis
Melhoria contínua: Suporte embutido para descoberta automática; um serviço global de registro de dados que permite a listagem de pipelines configurados
Código
Chaotic: Custo significativo de configuração para usar a tecnologia
Funcional: Alguns componentes reutilizáveis disponíveis
Melhoria contínua:
-
- Estruturas de base estão disponíveis e exigem código mínimo
- O pipeline pode ser configurado em formato legível por máquina
- Configuração zero
- Bibliotecas com semântica similar a outras soluções de pipeline mais usadas em equipes relacionadas
Documentação e melhores práticas
Chaotic: Documentação escassa ou desatualizada
Funcional: Documentação mínima de configuração para cada componente
Melhoria contínua:
-
- Documentação abrangente e atualizada
- Exemplos de treinamento para novos usuários
Testes de unidade e integração
Framework de teste unitário
Chaotic: Sem suporte ou framework de testes de unidade
Funcional:
-
- Os testes demoram muito para serem executados e frequentemente expiram por tempo excessivo
- Muitos requisitos de recursos
- Sem suporte para cobertura de código
- Fácil de alternar fontes de dados para testar um recurso
Melhoria contínua:
-
- Executa com sanitizadores (ASAN, TSAN, etc.)
- O gráfico de dependência de compilação é o menor possível
- Suporte para cobertura de código
- Fornece informações de depuração e resultados
- Sem dependências externas
- Biblioteca de geração de dados de teste embutida
Facilidade de configuração (isso tem relevância direta para o aspecto de escalabilidade do pipeline)
Chaotic:
-
- A configuração de teste não é suportada ou requer uma quantidade significativa de tempo para aprender, além de aprender sobre o próprio pipeline – por exemplo, os testes usam uma linguagem de programação diferente e uma
- API diferente do pipeline
Funcional: O primeiro teste de integração requer uma configuração significativa, mas os testes subsequentes são uma cópia/cola ou uma extensão do primeiro com substituições mínimas – por exemplo, um gerador de dados de teste pode ser minimamente ajustado para suportar a coleta de dados de teste para muitas aplicações de pipeline diferentes.
Melhoria contínua: Desacoplamento da configuração de produção, sem impedir a reutilização; mais fácil de definir a configuração de teste de integração e reutilizar partes relevantes da configuração de produção.
Suporte para frameworks de testes de integração (isso descreve o quão bem um pipeline deve interagir e suportar várias metodologias de teste de integração, que não são necessariamente parte do pipeline em si)
Chaotic: O uso de ferramentas em nuvem ou de terceiros de código aberto para diferenciação, monitoramento, teste de integração, etc., pode ser difícil de implementar e/ou exigir muitos recursos; requer ferramentas internas ou as ferramentas não existem
Funcional:
-
- Documentação mínima e exemplos de ferramentas e metodologias de teste de integração usadas em conjunto com o pipeline
- Grande quantidade de tempo necessária mesmo para conjuntos de dados com entrada mínima Difícil de acionar a execução sob demanda para cenários de teste
- Difícil de separar preocupações de produção de não produção (por exemplo, todos os logs de eventos vão para o serviço de log de produção)
Melhoria contínua:
-
- Suporte embutido para dados de entrada reduzidos
- Suporte para diferenciação de dados de saída (por exemplo, persistência de dados de teste de saída)
- Monitoramento configurável para validação da execução de teste
- Ampla documentação e exemplos de testes de integração construídos para o pipeline
Falhas no pipeline: prevenção e resposta
Um pipeline pode falhar por muitas razões, mas os culpados mais comuns são o atraso nos dados e a corrupção dos dados. Quando ocorre uma interrupção, encontrar e reparar o problema rapidamente reduzirá significativamente seu impacto. No Google, quando ocorre uma interrupção ou violação do SLO, acompanhamos métricas para MTTD (Tempo Médio para Detectar) e MTTR (Tempo Médio para Resolver). O acompanhamento dessas métricas indica o quão eficazes somos em detectar e reparar um problema. No postmortem que segue qualquer interrupção no Google, analisamos a causa da interrupção para extrair quaisquer padrões e abordar fontes de trabalho operacional excessivo.
Esta seção descreve alguns modos comuns de falha, métodos para responder efetivamente às falhas no pipeline e estratégias para ajudar a prevenir futuras falhas no pipeline.
Modos potenciais de falha
Atraso nos dados
Um pipeline pode falhar se sua entrada ou saída estiver atrasada. Sem as devidas precauções, um trabalho downstream pode começar a ser executado mesmo que não tenha os dados necessários. Dados obsoletos são quase sempre melhores do que dados incorretos. Se seu pipeline processar dados incompletos ou corrompidos, os erros se propagarão downstream. Restaurar ou reproces sar dados ruins leva tempo e pode prolongar uma interrupção. Em vez disso, se seu pipeline parar, aguardar pelos dados e depois retomar assim que os dados estiverem disponíveis, os dados permanecerão de alta qualidade. Criar dependências de dados que sejam respeitadas por todas as etapas é importante.
Dependendo do tipo de pipeline, o impacto dos dados atrasados pode variar desde dados de aplicativos obsoletos até pipelines parados. Nos pipelines de processamento em lote, cada etapa espera que seu antecessor termine antes de começar. Os sistemas de streaming são mais flexíveis: usando o processamento de tempo de evento, como o Dataflow, uma etapa downstream pode iniciar uma parte do trabalho assim que a parte correspondente upstream for concluída, em vez de esperar que todas as partes sejam concluídas.
Quando ocorre uma interrupção desse tipo, provavelmente será necessário notificar quaisquer serviços dependentes. Se a interrupção for visível para o usuário, você também poderá ter que notificar seus clientes. Ao depurar interrupções no pipeline, é útil ver o progresso das execuções atuais e anteriores do pipeline, e ter links diretos para arquivos de log e um diagrama do fluxo de dados. Também é útil poder rastrear uma unidade de trabalho pelo sistema enquanto analisa seus contadores e estatísticas.
Dados corrompidos
Se não detectados, dados corrompidos no pipeline (entrada e/ou saída) podem causar problemas visíveis ao usuário. Você pode contornar muitos problemas visíveis ao usuário tendo testes que identifiquem dados corrompidos no lugar, e usando lógica que o alerte para possíveis corrupções. Por exemplo, sistemas de pipeline podem implementar políticas de bloqueio e detecção de abuso/spam para filtrar automaticamente ou manualmente fontes ruins de dados.
Dados corrompidos podem ter muitas causas: bugs de software, incompatibilidade de dados, regiões indisponíveis, bugs de configuração, entre outros. Existem duas etapas principais envolvidas na correção de dados corrompidos:
- Mitigar o impacto ao evitar que mais dados corrompidos entrem no sistema.
- Restaurar seus dados a partir de uma versão anterior conhecida como válida, ou reprocesse para reparar os dados.
Se uma única região estiver fornecendo dados corrompidos, pode ser necessário suspender os trabalhos de servir e/ou processar dados dessa região. Se um bug de software ou configuração for o culpado, pode ser necessário reverter rapidamente o binário relevante. Frequentemente, a corrupção de dados pode causar janelas de dados incorretos que precisam ser reproces sadas assim que o problema subjacente for corrigido, como corrigir um bug de software em um binário de pipeline. Para reduzir o custo do reproces samento, considere o reproces samento seletivo – leia e processe apenas as informações de usuário ou conta impactadas pela corrupção de dados. Alternativamente, você pode persistir alguns dados intermediários que podem servir como um checkpoint para evitar o reproces samento de um pipeline de ponta a ponta.
Se a saída do seu pipeline estiver corrompida, os trabalhos downstream podem propagar os dados corrompidos ou os trabalhos de serviço podem fornecer dados incorretos. Mesmo com as melhores práticas de teste, desenvolvimento e liberação implementadas, um bug de software ou configuração pode introduzir corrupção de dados. Recomendamos que você planeje para essa eventualidade e tenha a capacidade de reproces sar e restaurar rapidamente seus dados. A recuperação desse tipo de corrupção de dados é intensiva em trabalho e difícil de automatizar.
Possíveis causas
Dependências do pipeline
Quando você está tentando determinar a causa de uma interrupção, é útil investigar as dependências do pipeline, como armazenamento, sistemas de rede ou outros serviços. Essas dependências podem estar limitando suas solicitações/tráfego ou, se estiverem sem recursos, recusando novos dados. A taxa de entrada/saída pode diminuir por uma variedade de razões:
- O destino de saída ou armazenamento pode estar recusando gravações de um conjunto de dados.
- Pode haver uma faixa de dados específica com alto tráfego que não consegue ser concluída.
- Pode haver um bug de armazenamento.
Alguns problemas de dependência do pipeline não se resolverão por si mesmos. É importante abrir um chamado ou bug e permitir tempo suficiente para adicionar mais recursos ou resolver padrões de tráfego. Implementar balanceamento de carga e descartar dados de baixa prioridade pode ajudar a mitigar o impacto.
Aplicação ou configuração do pipeline
Uma falha no pipeline pode ser resultado de um gargalo, um bug em seus trabalhos no pipeline, ou um bug nas próprias configurações (por exemplo, processamento intensivo de CPU, regressão de desempenho, falhas por falta de memória, dados abusivos propensos a hotspots, ou configurações que apontam para locais de entrada/saída incorretos). Dependendo da causa, existem várias soluções possíveis:
- Reverter o binário/configuração, escolher uma correção específica ou corrigir quaisquer problemas de permissão.
- Considerar a reestruturação dos dados que estão causando o problema.
Erros de aplicação ou configuração podem introduzir incorreções nos dados ou levar a atrasos nos dados. Esses tipos de erros são as causas mais comuns de interrupções. Recomendamos dedicar tempo ao desenvolvimento do pipeline e garantir que novos binários e configurações tenham um bom desempenho em um ambiente de não produção antes de serem totalmente implantados.
Crescimento inesperado de recursos
Um aumento repentino e não planejado na carga do sistema pode fazer com que um pipeline falhe. Você pode precisar de recursos adicionais não planejados para manter seu serviço em funcionamento. A escalabilidade automática dos trabalhos da sua aplicação pode ajudar a atender à demanda da nova carga, mas você também deve estar ciente de que um aumento na carga do pipeline também pode sobrecarregar as dependências downstream. Você também pode precisar planejar mais recursos de armazenamento e/ou rede.
Um bom planejamento de recursos e previsão precisa de crescimento podem ajudar nesses casos, mas tais previsões nem sempre serão corretas. Recomendamos familiarizar-se com o processo de solicitação de recursos adicionais de emergência. Dependendo da natureza da implantação do seu pipeline e da quantidade de recursos necessários, o tempo necessário para adquirir esses recursos pode ser substancial. Portanto, recomendamos preparar soluções intermediárias para manter seu serviço em funcionamento – por exemplo, priorizar diferentes classes de dados através do seu pipeline.
Interrupção a nível regional
Uma interrupção regional é prejudicial para todos os pipelines, mas os pipelines com uma única localização são particularmente vulneráveis. Se o seu pipeline é executado em uma única região que repentinamente se torna indisponível, o pipeline será interrompido até que a região volte a funcionar. Se você tiver pipelines com várias localizações e failover automático, sua resposta pode ser tão simples quanto retirar o processamento ou o serviço de uma região afetada até que a interrupção termine. Quando uma região está inativa, os dados podem ficar perdidos ou atrasados, resultando em saída incorreta do seu pipeline. Como resultado, a correção da saída de dados de quaisquer trabalhos ou serviços dependentes pode ser comprometida.
Estudo de caso: Spotify
por Igor Maravić
O Spotify é a principal empresa de streaming de música do mundo. Todos os dias, dezenas de milhões de pessoas usam o Spotify para ouvir suas músicas favoritas, compartilhar música com seus amigos e descobrir novos artistas.
Este estudo de caso descreve nosso sistema de entrega de eventos, que é responsável por coletar de forma confiável os dados de instrumentação gerados pelas aplicações do Spotify. Os dados produzidos por este sistema nos ajudam a entender melhor nossos usuários finais e a fornecer a eles a música certa no momento certo.
Neste estudo de caso, um “cliente” refere-se às equipes de desenvolvimento dentro do Spotify que utilizam dados do sistema de entrega de eventos. “Usuário final” refere-se às pessoas que utilizam o serviço do Spotify para ouvir música.
Eventos Delivery
Nos referimos às interações dos usuários finais como eventos. Cada vez que um usuário ouve uma música, clica em um anúncio ou segue uma playlist, registramos um evento. O Spotify captura e publica centenas de bilhões de eventos (de diversos tipos) para nossos servidores diariamente. Esses eventos têm muitos usos no Spotify, desde análise de testes A/B até exibição de contagens de reprodução e alimentação de playlists de descoberta personalizadas. Mais importante ainda, nós pagamos royalties aos artistas com base nos eventos entregues. É imperativo que tenhamos um meio confiável de armazenamento e entrega de eventos.
Antes de podermos processar os dados do evento, esses dados precisam ser coletados e entregues ao armazenamento persistente. Utilizamos um sistema de entrega de eventos para coletar e persistir de forma confiável todos os eventos publicados. O sistema de entrega de eventos é um dos pilares centrais de nossa infraestrutura de dados, pois quase todo o nosso processamento de dados depende – direta ou indiretamente – dos dados que ele entrega.
Todos os eventos entregues são particionados por tipo e horário de publicação. Conforme mostrado na Figura 13-5, os eventos publicados durante qualquer hora específica são agrupados e armazenados em um diretório designado, chamado de “bucket” horário entregue. Esses “buckets” são então agrupados em diretórios de tipos de eventos. Este esquema de particionamento simplifica o controle de acesso aos dados, propriedade, retenção e consumo no Spotify.
Figura 13-5. Buckets de horários entregues
Os “buckets” de horários são a única interface que nossos trabalhos de dados têm com o sistema de entrega de eventos. Como resultado, medimos o desempenho e definimos SLOs para nosso sistema de entrega de eventos com base em quão bem entregamos os buckets de horários por tipo de evento.
Projeto e arquitetura do sistema de entrega de eventos
Nossos baldes horários residem no Google Cloud Storage (GCS). No início do processo de design, decidimos desacoplar a coleta de dados da entrega de dados dentro do sistema. Para alcançar isso, usamos uma fila persistente distribuída globalmente, o Google Cloud Pub/Sub, como uma camada intermediária. Uma vez desacoplados, a coleta e a entrega de dados agem como domínios de falha independentes, o que limita o impacto de quaisquer problemas de produção e resulta em um sistema mais resiliente. A Figura 13-6 representa a arquitetura do nosso sistema de entrega de eventos.
Figura 13-6. Arquitetura do sistema de entrega de eventos
Coleta de dados
Os eventos produzidos são agrupados por tipos de evento. Cada tipo de evento descreve uma ação do usuário no aplicativo Spotify. Por exemplo, um tipo de evento poderia se referir a um usuário se inscrevendo em uma playlist, enquanto outro tipo de evento poderia se referir a um usuário iniciando a reprodução de uma música. Para garantir que tipos de eventos separados não impactem uns aos outros, o sistema possui isolamento completo por tipo de evento. Eventos individuais de diferentes tipos de evento são publicados em tópicos alocados no Google Cloud Pub/Sub. A publicação é realizada por nossos microsserviços, que são executados tanto nos datacenters do Spotify quanto no Google Compute Engine (GCE). Para serem entregues, cada fluxo de eventos publicado é tratado por uma instância dedicada de um processo ETL.
Extração, Transformação e Carregamento (ETL)
O processo ETL é responsável por entregar os eventos publicados aos baldes horários corretos no GCS. O processo ETL possui três etapas/componentes:
- Um microsserviço dedicado consome eventos do fluxo de eventos.
- Outro microsserviço atribui eventos às suas partições horárias.
- Um trabalho de dados em lote executado no Dataproc deduplica eventos de suas partições horárias e os persiste em sua localização final no GCS.
Cada componente do ETL tem uma única responsabilidade, o que torna os componentes mais fáceis de desenvolver, testar e operar.
Entrega de dados
A entrega de tipos de eventos é habilitada ou desabilitada dinamicamente diretamente por nossos clientes (outras equipes de engenharia no Spotify). A entrega é controlada por meio de uma configuração simples. Na configuração, os clientes definem quais tipos de eventos devem ser entregues. Conforme a entrega de cada tipo de evento é ligada ou desligada, um microsserviço adquire e libera dinamicamente os recursos do Google GCE nos quais o ETL é executado. O código a seguir mostra um exemplo de tipo de evento que um cliente pode habilitar/desabilitar:
events: -CollectionUpdate -AddedToCollection -RemovedFromCollection
Quando um cliente habilita a entrega de um novo tipo de evento, não sabemos antecipadamente qual quantidade de recursos é necessária para garantir a entrega. Consequentemente, determinar manualmente os recursos necessários é muito caro. Para alcançar a utilização ótima de recursos para a entrega de diferentes tipos de eventos, utilizamos o Autoscaler do GCE.
Operação do Sistema de Entrega de Eventos
Definir e comunicar SLOs para nosso sistema de entrega de eventos ajuda de três maneiras:
Design e desenvolvimento
Ao desenvolver nossos sistemas, ter SLOs claros em vigor nos dá metas para trabalhar. Essas metas nos ajudam a fazer escolhas de design pragmáticas e otimizar nossos sistemas para simplicidade.
Identificar problemas de desempenho
Uma vez que nossos sistemas são implantados em produção, os SLOs nos ajudam a identificar quais partes do sistema não estão funcionando bem e onde precisamos concentrar nossos esforços.
Definir expectativas do cliente
Os SLOs nos permitem gerenciar as expectativas dos nossos clientes e evitar solicitações de suporte desnecessárias. Quando os limites do nosso sistema estão claros para nossos clientes, eles estão capacitados a decidir como projetar, construir e operar seus próprios sistemas que dependem dos nossos dados.
Nós fornecemos aos nossos clientes três tipos de SLO para nosso sistema de entrega de eventos: pontualidade, completude e assimetria (discutida a seguir). Esses SLOs são baseados em intervalos de dados por hora fornecidos pelo GCS. Para ser o mais objetivo possível e evitar inflar a entrega de eventos com recursos que não têm nada a ver com ela, medimos todos os SLOs usando sistemas externos independentes (por exemplo, Datamon, uma ferramenta de visualização de dados explicada na próxima seção).
Pontualidade
Nosso SLO de pontualidade é definido como o atraso máximo na entrega de um intervalo de dados por hora. O atraso na entrega é calculado como a diferença de tempo entre quando o intervalo foi entregue e o momento teórico mais cedo em que o intervalo poderia ter sido fechado. A Figura 13-7 fornece um exemplo desse atraso na entrega. O diagrama mostra os intervalos para as horas 12, 13 e 14. Se o intervalo para a hora 13 foi fechado às 14:53, diríamos que o atraso no fechamento foi de 53 minutos.
Figura 13-7. Particionamento do tempo do evento
A pontualidade na entrega de dados é a métrica que usamos para avaliar o desempenho de nossos pipelines de dados. Para medir e visualizar a pontualidade, usamos uma ferramenta chamada Datamon, nossa ferramenta interna de monitoramento de dados que foi construída em torno da noção de intervalos por hora. A Figura 13-8 mostra uma interface de usuário típica do Datamon. Cada retângulo verde (em escala de cinza, a maioria dos retângulos) representa um intervalo por hora pontual. Retângulos cinza (agrupados aqui no lado direito) indicam intervalos que não foram entregues, enquanto retângulos vermelhos (3 retângulos escuros na linha superior) indicam intervalos que não foram entregues dentro do SLO necessário. Dias em que todas as horas foram entregues com sucesso são mostrados como um único retângulo verde.
Figura 13-8. Datamon para o sistema de monitoramento de dados do Spotify
Os trabalhos de dados downstream não podem iniciar seu processamento até que os intervalos horários nos quais dependem sejam entregues. Cada trabalho de dados verifica periodicamente o status de entrega de suas dependências antes de processar os dados. Qualquer atraso na entrega afeta a pontualidade dos trabalhos downstream. Nossos clientes se preocupam profundamente com a entrega de dados de forma pontual. Para nos ajudar a priorizar a entrega de eventos durante um incidente, o SLO de pontualidade do nosso sistema de entrega de eventos é dividido em três níveis de prioridade: alta, normal e baixa. Nossos clientes configuram para o nível apropriado para o tipo de evento deles.
Assimetria
Definimos nosso SLO de assimetria como a porcentagem máxima de dados que podem ser erroneamente posicionados diariamente. Assimetria (e completude) são conceitos específicos do nosso sistema de entrega de eventos e não estão presentes em nossos outros pipelines de dados. Definir um SLO para esses conceitos foi um requisito-chave quando estávamos projetando nosso sistema de entrega de eventos, pois ele processa (entre outros tipos de eventos) eventos relacionados a finanças. Para todos os outros eventos, a entrega com o melhor esforço é suficiente e não expomos um SLO correspondente. Se um evento é ou não relacionado a finanças é determinado pela configuração do cliente.
Para determinar quando um intervalo horário deve ser entregue, nosso sistema de entrega de eventos utiliza heurísticas. Por definição, heurísticas nem sempre estão completamente corretas. Como resultado, eventos não entregues de intervalos previamente entregues podem ser entregues a um intervalo horário futuro incorreto. Esse evento mal posicionado é chamado de desvio. Um desvio pode impactar negativamente os trabalhos, pois eles podem relatar valores inicialmente subestimados e depois superestimados para alguns períodos de tempo. A Figura 13-9 mostra um exemplo de entrega de dados com desvio.
Figura 13-9. Entrega de dados com desvio
Completude
Eventos podem ser perdidos em um sistema distribuído de várias maneiras, por exemplo, uma nova versão do nosso software pode conter um bug, um serviço em nuvem pode ficar inativo, ou um desenvolvedor pode deletar acidentalmente alguns eventos persistidos. Para garantir que sejamos alertados sobre a perda de dados, medimos a completude. Definimos completude como a porcentagem de eventos que são entregues após serem publicados com sucesso no sistema.
Relatamos a assimetria e a completude diariamente. Para medir esses valores, usamos um sistema de auditoria interno que compara as contagens de todos os eventos publicados e entregues. Qualquer inconsistência é relatada e tomamos as medidas apropriadas.
Para garantir os SLOs de pontualidade, assimetria e completude, atribuímos eventos aos nossos intervalos horários no momento em que foram recebidos em nossos servidores, e não quando foram produzidos nos clientes. Se nossos usuários estiverem no modo offline, os eventos produzidos podem ser armazenados em buffer por até 30 dias nos clientes antes de serem publicados. Além disso, os usuários podem modificar o horário do sistema em seu dispositivo, o que pode resultar em eventos com carimbos de data e hora imprecisos. Por esses motivos, usamos o carimbo de data e hora dos servidores do Spotify.
Não fornecemos nenhum SLO relacionado à qualidade ou precisão dos eventos entregues por meio de nosso sistema de entrega de eventos. Observamos que, na maioria dos casos, a qualidade depende do conteúdo de cada evento, que é preenchido pela lógica de negócios de nossos clientes. Para permitir que nosso sistema escale com o número de clientes, mantemos o foco exclusivamente na entrega de dados. Nesse sentido, usamos a analogia de que a entrega de eventos deve se comportar como um serviço postal: sua correspondência deve ser entregue pontualmente, intacta e não aberta. Deixamos a responsabilidade de fornecer SLOs de qualidade para nossas equipes internas que possuem a lógica de negócios e, portanto, entendem o conteúdo dos dados.
Integração e Suporte ao Cliente
Muitas equipes do Spotify interagem diariamente com o sistema de entrega de eventos. Para incentivar a adoção e diminuir a curva de aprendizado, tomamos as seguintes medidas para simplificar a interação do usuário com o sistema de entrega de eventos:
Entrega de eventos como um serviço totalmente gerenciado
Queríamos evitar expor a complexidade do sistema aos nossos clientes, permitindo que eles se concentrem nos problemas específicos que estão tentando resolver. Nos esforçamos para ocultar quaisquer complexidades do sistema por trás de uma API bem definida e fácil de entender.
Funcionalidade limitada
Para manter nossas APIs simples, oferecemos suporte apenas a um conjunto limitado de funcionalidades. Os eventos podem ser publicados apenas em um formato específico interno e podem ser entregues apenas em intervalos horários com um único formato de serialização. Essas APIs simples cobrem a maioria dos nossos casos de uso.
A entrega de cada evento precisa ser explicitamente habilitada
Quando um cliente habilita a entrega de um evento, eles definem se o evento é relacionado a finanças e seus requisitos de pontualidade associados. Além disso, a propriedade do evento precisa ser explicitamente definida como parte do processo de habilitação. Acreditamos firmemente que responsabilizar nossas equipes internas pelos eventos que produzem resulta em maior qualidade de dados. A propriedade explícita dos eventos também nos dá um canal de comunicação claro durante incidentes.
Documentação
Independentemente de quão simples seja interagir com o sistema, uma boa documentação é necessária para fornecer uma boa experiência ao cliente. A documentação abaixo do padrão e desatualizada é um problema comum em empresas de ritmo acelerado como o Spotify. Para lidar com isso, tratamos nossa documentação como qualquer outro produto de software: todos os pedidos de suporte que chegam à nossa equipe são tratados como problemas com nossa documentação ou como problemas no produto real. A maioria dos pedidos de suporte está relacionada às APIs públicas do sistema. Alguns exemplos de perguntas que tentamos responder ao escrever nossa documentação incluem:
- Como é habilitada a entrega de um tipo de evento?
- Onde os dados são entregues?
- Como os dados são particionados?
- Quais são nossos SLOs?
- Que tipo de suporte nossos clientes devem esperar durante incidentes?
- Nosso objetivo é minimizar a quantidade de solicitações de suporte que recebemos à medida que nossa base de clientes cresce.
- Monitoramento do sistema
Monitorar nossos SLOs fornece insights de alto nível sobre a saúde geral do sistema. Nossa solução de monitoramento abrangente e confiável garante que sempre sejamos alertados quando algo der errado. O principal problema ao usar uma violação de SLO como critério para monitoramento é que somos alertados depois que nossos clientes foram afetados. Para evitar isso, precisamos de monitoramento operacional suficiente do nosso sistema para resolver ou mitigar problemas antes que um SLO seja violado.
Monitoramos os vários componentes do nosso sistema separadamente, começando com métricas básicas do sistema e depois avançando para métricas mais complexas. Por exemplo, monitoramos o uso da CPU como um sinal da saúde da instância. O uso da CPU nem sempre é o recurso mais crítico, mas funciona bem como um sinal básico.
Às vezes, o monitoramento do sistema é insuficiente quando estamos tentando entender e corrigir problemas de produção. Para complementar nossos dados de monitoramento, também mantemos logs de aplicativos. Esses logs contêm informações importantes relacionadas à operação e saúde do componente que descrevem. Cuidamos para garantir que coletamos apenas a quantidade correta de dados de registro, pois é fácil para logs irrelevantes afogarem os úteis. Por exemplo, uma implementação ruim de registro pode registrar todas as solicitações recebidas por um componente de alto volume que lida com solicitações recebidas. Supondo que a maioria das solicitações seja semelhante, registrar cada solicitação não adiciona muito valor. Além disso, quando muitas solicitações são registradas, torna-se difícil encontrar outras entradas de log, o disco enche mais rápido e o desempenho geral do nosso serviço começa a degradar. Uma abordagem melhor é limitar a quantidade de solicitações registradas ou registrar apenas solicitações interessantes (como aquelas que resultam em exceções não tratadas).
Depurar componentes em produção lendo os logs de aplicativos é desafiador e deve ser usado como último recurso.
Planejamento de capacidade
A operação confiável 24 horas por dia do sistema de entrega de eventos requer a quantidade correta de recursos alocados, especialmente porque os componentes são implantados em um único projeto do GCP e compartilham um pool de cota comum. Utilizamos o planejamento de capacidade para determinar quantos recursos cada componente do sistema precisa.
Para a maioria dos componentes do nosso sistema, o planejamento de capacidade é baseado no uso da CPU. Provisionamos cada componente para ter 50% de uso da CPU durante as horas de pico. Essa provisão atua como uma margem de segurança que permite que nosso sistema lide com picos inesperados de tráfego. Quando o Spotify tinha seus próprios data centers, fornecíamos recursos estáticos para cada componente. Isso levava a um desperdício de recursos durante as horas fora do pico e uma incapacidade de lidar com grandes picos de tráfego. Para melhorar a utilização de recursos, utilizamos o Autoscaler do GCE para alguns de nossos componentes sem estado.
Tivemos algumas dificuldades iniciais ao implementar o Autoscaler; em certas condições, o Autoscaler pode causar falhas. Por exemplo, usamos o uso da CPU como uma métrica para realizar o dimensionamento automático. O Autoscaler depende de uma forte correlação entre o uso da CPU e a quantidade de trabalho realizado por cada instância do componente. Se a relação for quebrada – seja pela adição de daemons que consomem muita CPU a cada instância do componente ou devido às instâncias do componente consumindo extensivamente CPU sem realizar nenhum trabalho – o Autoscaler iniciará muitas instâncias.
Quando o Autoscaler se depara com o aumento constante do uso da CPU que não possui correlação com a quantidade de trabalho realizado, ele escalonará indefinidamente até utilizar todos os recursos disponíveis. Para evitar que o Autoscaler consuma toda a nossa cota, implementamos algumas soluções alternativas:
- Limitamos o número máximo de instâncias que o Autoscaler pode usar.
- Restringimos fortemente o uso da CPU de todos os daemons em execução em uma instância.
- Reduzimos agressivamente o uso da CPU de um componente assim que detectamos que nenhum trabalho útil está sendo realizado.
Mesmo ao usar o Autoscaler, precisamos realizar o planejamento de capacidade. Precisamos garantir que tenhamos cota suficiente e que o número máximo de instâncias que o Autoscaler pode usar seja configurado alto o suficiente para atender ao tráfego durante os picos, mas baixo o suficiente para limitar o impacto do dimensionamento automático “descontrolado”.
Processo de desenvolvimento
Para lançar novos recursos e melhorias rapidamente, desenvolvemos o sistema de entrega de eventos (mostrado na Figura 13-10) seguindo o processo de integração contínua e entrega contínua (CI/CD). De acordo com esse processo, mudanças válidas, comprovadas ou revisadas no sistema são implantadas assim que são feitas. Ter cobertura de teste suficiente é um pré-requisito para que cada mudança seja implantada com sucesso sem impactar negativamente nossos SLOs.
Escrevemos testes seguindo a filosofia da “pirâmide de testes”. Isso significa que para cada um de nossos componentes, temos muitos testes unitários que se concentram no funcionamento interno dos componentes, além de um número menor de testes de integração que se concentram na API pública dos componentes. No nível mais alto da pirâmide de testes, temos um teste de ponta a ponta em todo o sistema. Neste teste de ponta a ponta, todos os componentes são tratados como caixas pretas para que o sistema no ambiente de teste se assemelhe o máximo possível ao ambiente de produção.
Após o desenvolvimento inicial, cada alteração passa por uma revisão por pares. Como parte do processo de revisão, todos os testes são executados em um servidor de CI/CD compartilhado, e os resultados são apresentados aos desenvolvedores. As alterações só podem ser mescladas após a aprovação dos revisores e todos os testes terem sido aprovados com sucesso. Assim que a alteração é mesclada, o processo de implantação é acionado.
O sistema de entrega de eventos é um componente crítico na infraestrutura do Spotify. Se parasse de entregar dados, todo o processamento de dados no Spotify seria interrompido. Por esse motivo, decidimos adotar uma abordagem mais conservadora para as implantações e implantar cada alteração em estágios. Exigimos uma aprovação manual antes que uma implantação possa avançar de um estágio para outro.
Figure 13-10. Development process
Durante a primeira etapa de implantação, a alteração é implantada no ambiente de pré-produção. Este sistema de pré-produção de baixo risco não lida com o tráfego de produção. Para fins de teste, uma fração representativa do tráfego de produção é espelhada no sistema de pré-produção, que é uma réplica do sistema em execução na produção. Na segunda etapa de implantação, a alteração é implantada em um pequeno subconjunto das instâncias de produção, ou canários. Realizamos uma implantação completa apenas após garantir que tudo correu bem, tanto no ambiente de pré-produção quanto nos canários (consulte o Capítulo 16).
Manuseio de incidentes
Ao lidar com um incidente, nossa primeira prioridade é mitigar o dano e retornar o sistema a um estado estável anterior. Para evitar piorar a situação, nos abstemos de implantar quaisquer mudanças importantes em nossos componentes durante um incidente. A exceção a esta regra é se concluirmos que o incidente foi causado por código novo recentemente implantado. Nesses casos, imediatamente revertemos o sistema para uma versão anterior que estava funcionando.
Hoje, as falhas operacionais mais comuns que encontramos são causadas ou por uma falha no sistema (por exemplo, introduzimos um bug de software ou uma regressão de desempenho) ou por uma falha em um serviço externo no qual dependemos (por exemplo, uma atualização de uma API de serviço não é compatível com versões anteriores ou um serviço viola seu SLO). Usamos muitos serviços do Google Cloud e serviços internos testados em batalha, como o Cloud Pub/Sub e o Helios, para acelerar o desenvolvimento de nosso sistema e reduzir nossa carga operacional. Em caso de incidente causado por um serviço externo, temos uma equipe de plantão dedicada que fornece suporte. Uma desvantagem de usar serviços externos é que não podemos fazer muito para mitigar o problema por conta própria. Além disso, comunicar o problema a um terceiro leva tempo valioso durante um incidente. No entanto, acreditamos que a capacidade de delegar responsabilidades vale a sensação ocasional de impotência.
O comportamento inesperado do sistema sob carga pesada é outra fonte comum de falhas operacionais. Testar serviços sob condições exatas de produção é impossível, portanto, é difícil prever todos os casos extremos que podem ocorrer. Também pode ser difícil emular a carga que nossos componentes enfrentam em produção. Cargas pesadas em combinação com casos extremos imprevistos podem levar a cenários de falha interessantes, como o exemplo do Autoscaler descrito anteriormente no “Planejamento de Capacidade”.
As falhas operacionais do sistema podem fazer com que nossos SLOs sejam violados. Se nosso SLO de frescor de dados for violado, nenhuma ação do cliente é esperada; os clientes simplesmente precisam esperar que seus dados cheguem. No entanto, se nossos SLOs de assimetria ou completude forem violados, podemos precisar envolver os clientes, pois a qualidade dos dados é comprometida. Quando detectamos problemas de completude ou assimetria, os eventos afetados precisam ser reprocessados para serem entregues corretamente:
- Para lidar com a incompletude, os eventos precisam ser entregues novamente a partir do último ponto conhecido como correto.
- Para lidar com a assimetria excessiva, os eventos já entregues são reorganizados e atribuídos aos seus intervalos horários corretos.
Tanto a redelivery quanto o reshuffling dos eventos são feitos manualmente. Depois que os eventos entregues são modificados, aconselhamos fortemente nossos clientes a reprocessá-los para produzir dados de qualidade suficiente.
Resumo
O sistema de entrega de eventos do Spotify evoluiu ao longo dos anos. Como iterações anteriores eram muito menos confiáveis, nossos engenheiros eram chamados várias vezes durante a noite. Passamos a maioria dos nossos ciclos de desenvolvimento em remediações de incidentes e postmortems. Ao projetar a encarnação atual, focamos em construir um sistema modularizado que faz uma coisa principal muito bem: entregar eventos. Além disso, queríamos fornecer a entrega de eventos como um produto para o restante do Spotify. Para alcançar isso, precisávamos definir e atender SLOs para que pudéssemos estabelecer expectativas claras para nossos clientes.
Empregamos uma variedade de estratégias para manter o serviço funcionando— desde procedimentos de plantão bem documentados até o uso de serviços externos bem testados (como o Google Cloud Pub/Sub). Além disso, uma única equipe é responsável pelo desenvolvimento e operação do sistema ao longo de todo o seu ciclo de vida. Essa estrutura de desenvolvimento nos permite utilizar a experiência da equipe que adquirimos ao manter o sistema para continuamente melhorá-lo.
Como resultado desses esforços, agora temos um sistema confiável que nos permite focar nosso tempo em atender SLOs de completude, assimetria e pontualidade mais ambiciosos. Isso resulta em uma melhor usabilidade e uma melhor experiência geral do cliente.
Conclusão
Aplicar as melhores práticas de Engenharia de Confiabilidade de Sites (SRE) aos pipelines podem ajudá-lo a fazer escolhas de design inteligentes e desenvolver ferramentas de automação para que os pipelines sejam fáceis de operar, escalarem de forma mais eficaz e sejam mais confiáveis. O sistema de entrega de eventos do Spotify é um exemplo de um pipeline construído e operado com os princípios fundamentais da SRE em mente, utilizando uma variedade de tecnologias — desde internas, Google Cloud e de terceiros — escolhidas para atender à necessidade do cliente de processamento de dados oportunos. Sem o devido cuidado com as melhores práticas operacionais, os pipelines podem estar mais propensos a falhas e exigir muito trabalho manual, especialmente durante períodos de crescimento, migrações, lançamentos de recursos ou limpeza após falhas. Como em qualquer design de sistema complexo, é importante conhecer seus requisitos e os SLOs que você escolheu manter, avaliar a tecnologia disponível e documentar o design e como realizar tarefas comuns.