Gerência de Projetos Dev & TI

4 jun, 2018

Criando um fluxo de trabalho de previsão baseado em trabalho para detecção de anomalia de observabilidade

Publicidade

Na Uber, combinamos o monitoramento de sistemas em tempo real com mecanismos inteligentes de alerta para garantir a disponibilidade e a confiabilidade de nossos aplicativos. Em nosso esforço para capacitar nossos engenheiros a criarem alertas mais precisos, a equipe de Aplicações de Observabilidade da Uber, tentou introduzir backtesting de alerta – a capacidade de determinar: se, e quando, uma determinada configuração de alerta teria sido disparada no passado, facilitando a previsão de alertas futuros.

O objetivo de nossa equipe de criar essa funcionalidade nos motivou a reformular completamente o fluxo de trabalho de nossa plataforma de detecção de anomalias, reimaginando o processo pelo qual os engenheiros podem solicitar novas previsões e garantir a contabilidade de períodos anteriores. O pipeline resultante, que gira em torno de uma noção formalizada de um trabalho de previsão, permite o preenchimento intuitivo e eficiente de previsões, abrindo caminho para alertas mais inteligentes.

uMonitor e backtesting de alerta

Para manter a confiabilidade de nossos serviços, nossos engenheiros de plantão precisam ser notificados e alertados o mais rápido possível no caso de uma falha ou outro comportamento anormal. O uMonitor, plataforma de autoria e gerenciamento de alertas da Uber Engineering, oferece essa supervisão ao permitir que engenheiros definam alertas sobre as principais métricas de seus serviços.

Em essência, um alerta do uMonitor consiste em uma consulta de métrica, um conjunto de limites de alerta e um conjunto de uma ou mais ações a serem executadas, caso a métrica monitorada exceda seus limites correspondentes. Essas ações podem ser consideradas como ganchos que, por exemplo, podem permitir que o alerta em questão notifique a equipe responsável por esse serviço com uma push notification para desvios extremos da métrica, enquanto enviam somente um e-mail não invasivo para desvios menores.

Além desses atributos de linha de base, o uMonitor fornece aos autores um conjunto de opções de configurações robustas que reforçam o comportamento de alerta de maneiras mais sutis – por exemplo, alertando sobre limites dinâmicos (em oposição aos tradicionais estáticos) por meio da detecção de anomalias, ou suprimindo alertas até que uma violação de limite tenha sido mantida por um determinado período de tempo.

Figura 1: O uMonitor permite que os engenheiros da Uber sejam notificados sobre comportamento anormal na produção.

Como resultado dessa flexibilidade, os alertas na Uber são compostos de inúmeros parâmetros de flutuação livre, todos controlados pelo autor do alerta. Na verdade, o uMonitor é tão personalizável que a equipe de Aplicativos de Observabilidade até o utiliza para monitorar a si mesma (por meio de uma instância separada, é claro). Por exemplo, a equipe configurou um alerta que mostra seu engenheiro de plantão sempre que as execuções de alertas globais são interrompidas – um indicador de que algo provavelmente deu errado no próprio sistema.

Com essa flexibilidade, no entanto, vem a complexidade: para algumas equipes, gerenciar, manter e refinar seus alertas no uMonitor pode se tornar um projeto tão complexo quanto os serviços que estão monitorando. Além disso, funcionalidades como a detecção de anomalias, embora sejam cruciais para certas classes de alertas, introduzem um nível de opacidade ao alerta que pode ser difícil para os engenheiros aceitarem ou confiarem.

Assim, às vezes pode ser difícil para um engenheiro antecipar as circunstâncias sob as quais um determinado alerta pode disparar ou determinar a otimização de sua configuração; às vezes, uma solução para um alerta abaixo do ideal pode ser tão simples quanto uma alteração de configuração que está se escondendo à vista.

Com esse desafio em mente, procuramos implementar o backtesting de alerta no uMonitor. Esse recurso permite que os engenheiros executem uma determinada configuração de alerta para visualizar como ela teria respondido ao comportamento de produção recente e histórico, permitindo um ciclo de feedback responsivo pelo qual um engenheiro pode ajustar gradualmente seu alerta antes de salvá-lo.

Acreditamos que o backtesting de alerta ajudará a remover parte da opacidade e desconexão inerente ao processo de autoria de alerta, permitindo que os engenheiros definam alertas mais precisos e acionáveis para os serviços da Uber.

Ineficiência isolada

Como a funcionalidade de detecção de anomalias do uMonitor depende dos limites dinâmicos gerados por uma camada de previsão separada, F3, não foi possível simplesmente liberar esse recurso para os usuários. Percebemos que o conjunto de suposições que sustentava o fluxo de trabalho atual da F3 nos impediu de fornecer backtesting de alerta para todos os usuários do uMonitor sem impor um fardo pesado ao sistema de métricas subjacente – o ecossistema de armazenamento e consulta ao qual a F3 oferece suporte e pelo qual é suportada.

Desde a sua criação no final de 2015, a F3 ofereceu um fluxo de trabalho exclusivamente iterativo. Em relação a um ponto específico no tempo, os consumidores podem solicitar uma nova previsão para o futuro próximo imediato. Por padrão, a única responsabilidade da F3 era gerar novas previsões, uma a uma, isoladamente.
Por outro lado, os alertas de backtesting que utilizam a detecção de anomalias exigem a garantia de que qualquer medida de série temporal seja totalmente coberta por previsões para um determinado intervalo de tempo.

O sistema ideal iria preencher as previsões históricas para quaisquer subintervalos que estão com elas faltando. Tal funcionalidade teria aplicações úteis. Por exemplo, considere um cenário em que um engenheiro gostaria de determinar se a detecção de anomalias ajudaria ou não a melhorar as taxas de sinal/ruído de seus alertas.

Esses alertas nunca foram submetidos à detecção de anomalias e, portanto, não têm previsões históricas pré-existentes no armazenamento de previsão para comparação. Com uma API de preenchimento dedicada, o engenheiro poderia simplesmente obter o intervalo de tempo desejado e solicitar que essas previsões históricas fossem criadas, fornecendo imediatamente valores de previsão de linha de base para comparação.

É certo que uma API de preenchimento de previsão dedicada não é estritamente necessária, uma vez que os consumidores podem simplesmente consultar o armazenamento de previsão, o que forneceria todas as informações necessárias para calcular os intervalos de tempo cujas previsões estão faltando e enviar as solicitações correspondentes para F3.

Na verdade, usamos esse método para implementar uma versão preliminar da funcionalidade de backtesting do uMonitor. Essa abordagem, entretanto, revelou uma ineficiência mais fundamental com a F3 que era impossível contornar, desde que as solicitações de previsão fossem tratadas de forma isolada.

Essa ineficiência diz respeito ao relacionamento da F3 com o sistema de métricas subjacente. Para cada solicitação de previsão, a F3 requer um conjunto específico de dados históricos para a métrica de série temporal correspondente; esse dado forma a base dos cálculos posteriores de previsão da F3.

Historicamente, a F3 consultou o sistema de métricas para essas janelas de dados históricos durante cada operação de previsão – mesmo quando os requisitos de dados para essas operações têm sobreposição significativa. Sob essa implementação ingênua, nossa equipe percebeu que, de modo assintótico, cerca de 90% de todos os dados solicitados seriam redundantes.

Figura 2: Ter tarefas de previsão que são responsáveis pelas mesmas séries temporais em intervalos de tempo adjacentes recupera seus próprios resultados de dados necessários em redundância séria, sobrecarregando o sistema de métricas.

Essa redundância representou uma quantidade não-trivial de sobrecarga adicional no sistema de métricas que nossa equipe decidiu que era inaceitável.

Concluímos que, se enviássemos backtesting de alerta para os usuários do uMonitor sem abordar esses problemas subjacentes, apresentaríamos o risco de picos ascendentes intermitentes e completamente arbitrários de carga no serviço de consulta de métrica. Se o serviço de consulta, em algum momento, não conseguisse responder por tal aumento, a F3 correria o risco de derrubá-lo completamente, sobrecarregando o próprio ecossistema que ele pretendia reforçar e fortalecer.

Otimizando em conjunto

Assim, os esforços da nossa equipe para implementar backtesting de alerta universal no uMonitor exigiram que voltássemos a abordar totalmente o fluxo de trabalho de previsão da F3. Precisávamos de um design que oferecesse suporte, não apenas para o fluxo de trabalho iterativo e canônico do serviço, mas também para um mecanismo separado de preenchimento em massa.

Observamos que a operação fundamental da F3 é produzir previsões para métricas de séries temporais que sejam válidas em intervalos de tempo limitados. Em outras palavras, embora o serviço pudesse executar individualmente operações de previsão separadas isoladamente, faltava uma camada de abstração que permitisse processar coletivamente essas operações.

Com esse requisito em mente, decidimos apresentar formalmente o serviço à noção de um trabalho de previsão. Essa nova abstração formalizada das operações fundamentais da F3 trouxe a capacidade de o serviço não apenas executá-las, mas também otimizá-las.

Delineando concretamente os atributos que definem e identificam exclusivamente uma operação de previsão atômica – atributos como a string de consulta correspondente à métrica de série temporal na previsão, o intervalo de tempo coberto, bem como o modelo específico ao qual os valores de previsão correspondem -, o serviço agora pode executar cálculos sobre esses trabalhos em conjunto. Esse novo recurso prepara o caminho para várias otimizações importantes que, coletivamente, levam a uma API muito mais eficaz, efetiva e intuitiva para o preenchimento de previsão.

Por exemplo, esse framework de trabalho permite que o serviço sincronize e coordene nativamente as operações de previsão. Ao acompanhar quais trabalhos foram iniciados, concluídos ou estão em andamento – seja em algum cache na memória ou em uma camada de banco de dados dedicada -, a F3 pode determinar se qualquer solicitação de entrada é redundante com outra que foi feita anteriormente ou está atualmente sendo processada.

Esse mesmo mecanismo também permite que o serviço opere em um nível mais alto de abstração do que seu caso de uso anterior, mais interativo. Considere, por exemplo, o caso em que um consumidor de F3 gostaria de garantir, para alguma métrica, que um intervalo de tempo inteiro tenha cobertura de previsão completa.

Com uma consciência de quais previsões foram criadas no passado, bem como quais estão sendo criadas atualmente, a F3 agora pode determinar com eficiência quais subgrades estão com previsões faltando e trabalhar para preencher as lacunas com o número apropriado e a especificação de trabalhos.

Figura 3: Mantendo um registro persistente de quais tarefas de previsão estão concluídas ou em andamento, a F3 pode inferir quais tarefas são necessárias para “preencher as lacunas”. Sobreposições entre tarefas em qualquer momento, como mostrado aqui, são toleráveis.

Como discutido anteriormente, esse framework de trabalho permite que a F3 opere em operações de previsão em conjunto. Esse recurso tem retorno operacional imediato, permitindo o pool de dados. Considere, por exemplo, um caso em que uma determinada solicitação de previsão resulta em várias tarefas de previsão, cada uma com seus próprios requisitos de dados históricos. Se essas tarefas cobrem intervalos de tempo adjacentes ou próximos, o preenchimento dos requisitos de dados de cada tarefa isoladamente resulta em redundância significativa no tráfego da rede e na carga da consulta.

Tarefas como abstrações sobre operações de previsão nos permitem, por exemplo, consolidar os requisitos de dados de cada tarefa em um conjunto abrangente de janelas de consulta, conforme ilustrado na Figura 4. Essa solicitação de dados consolidada pode ser enviada de uma só vez para o serviço de consulta, cortando de forma significativa a carga desnecessária e, ao mesmo tempo, garantindo que cada tarefa de previsão receba os dados de que precisa.

Figura 4: Permitir que as tarefas de previsão sub-selecionem seus dados após a consulta a partir de um conjunto comum minimiza a carga desnecessária no sistema de métricas devido à previsão.

Juntando tudo: o novo fluxo de trabalho

A introdução do tipo de dados do trabalho nos permite generalizar drasticamente o pipeline de previsão da F3 para suportar de forma nativa e eficiente o novo caso de uso de preenchimento. Embora o fluxo de trabalho iterativo tenha sido mantido praticamente inalterado, as otimizações para o fluxo de trabalho subjacente da F3 são significativas.

Agora, com um registro histórico de todas as tarefas de previsão anteriores e atualmente em execução, a F3 pode fornecer ao uMonitor e a outros consumidores uma API simples e intuitiva que solicita apenas o intervalo de tempo do qual eles gostariam de receber cobertura para uma determinada série temporal.

A F3 pode, então, simplesmente dividir esse intervalo de tempo em seus trabalhos necessários em relação àquelas subescalas que já foram contabilizadas – seja por previsões pré-existentes ou por aquelas que estão em andamento. Com esse conjunto de tarefas, o serviço agora pode calcular o conjunto abrangente mínimo de dados históricos que serão responsáveis por todos os seus requisitos.

Ao fazer uma única solicitação para o sistema de métricas para esses dados, o serviço pode particionar os dados conforme necessário, processando cada tarefa com apenas as fatias de subconjunto que elas exigem. Por fim, todos os trabalhos podem ter um registro de sua conclusão registrado no histórico do registro de tarefas da F3, comunicando-se efetivamente com todas as solicitações futuras de previsão de que esses intervalos de tempo são totalmente contabilizados e não precisam de processamento adicional.

Essa capacidade recém-introduzida para o pool de dados em nossa implementação de produção do F3 nos permitiu reduzir a carga do serviço no armazenamento de métricas da série temporal subjacente em até 90%.

Figura 6: Ao introduzir uma capacidade de sub-selecionar dados de um conjunto comum de dados históricos, reduzimos o fardo da F3 no armazenamento de métricas subjacentes em até 90% (o número não foi dimensionado).

Em outras palavras, a introdução de uma estrutura de dados simples no sistema foi a chave para um conjunto de otimizações e recursos derivados que, coletivamente, levaram não apenas a uma API de nível de serviço mais intuitiva, mas também aumentaram a confiabilidade e a eficiência.

Considerações finais

Apesar de suas armadilhas na previsão de séries temporais e na detecção de anomalias, esse projeto acabou se resumindo a uma questão de design de APIs. A necessidade do uMonitor de suportar backtesting de alertas que usam detecção de anomalias sem afetar negativamente outros componentes cruciais de nosso ecossistema de observabilidade nos forçou a redesenhar totalmente o fluxo de trabalho da F3, gerando questões como: por que tipo de informação ou detalhes os usuários são responsáveis? Da mesma forma, com esse conjunto de suposições, que tipo de interface faria mais sentido a partir das perspectivas deles?

Essa abordagem de projeto acabou sendo crucial para a criação de uma solução que fosse ao mesmo tempo produtiva e intuitiva. Ao decidir sobre o estado final ideal, independentemente do estado atual das coisas e de trabalhar ao contrário, conseguimos identificar exatamente quais recursos estavam ausentes do estado atual do serviço que permitiriam o comportamento que queríamos.

Esses recursos ausentes se resumiam a uma única estrutura de dados – uma que formalizava a operação fundamental do serviço e, portanto, permitia que o serviço fosse otimizado com base nela. Essa estrutura de dados, bem como as otimizações subsequentes que se basearam em sua ideia central, nos permitiram obter significantes declarações de escalabilidade para F3.

Como resultado desse projeto, a F3 e a nossa plataforma de Detecção de Anomalias de Observabilidade estão em uma posição muito melhor para atender às necessidades cada vez mais sofisticadas de alerta e monitoramento de nossos engenheiros e da plataforma Uber como um todo.

Se você deseja capacitar engenheiros da Uber para monitorar seus sistemas de produção, a equipe de Aplicações de Observabilidade adoraria falar com você.

***

Este artigo é do Uber Engineering. Ele foi escrito por Jonathan Jin. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/observability-anomaly-detection/