A demanda por lojas de recursos de aprendizagem de máquina de baixa latência é maior do que nunca, mas implementá-las em escala continua a ser um desafio. Isso ficou claro quando os engenheiros do ShareChat Ivan Burmistrov e Andrei Manakov tomaram o P99 CONF 23 para compartilhar como construíram uma loja de recursos de ML de baixa latência baseada no ScyllaDB. Este não é um estudo de caso ordenado onde a adoção de um novo produto salva o dia.É uma história de “leções aprendidas”, um olhar sobre o valor da otimização de desempenho implacável – com algumas importantes tomadas de engenharia. A implementação original do sistema caiu muito abaixo dos requisitos de escalabilidade da empresa. O objetivo final era suportar 1 bilhão de recursos por segundo, mas o sistema falhou sob uma carga de apenas 1 milhão. Com alguma solução de problema inteligente, a equipe o tirou embora. Vejamos como seus engenheiros conseguiram pivotar do fracasso inicial para cumprir seu objetivo de desempenho elevado sem escalar o banco de dados subjacente. Obsessão com otimização de desempenho e engenharia de baixa latência? junte-se aos seus colegas no P99 24 CONF, uma conferência virtual gratuita e altamente técnica sobre “desempenho de todas as coisas”. Michael Stonebraker, criador do Postgres e professor do MIT Bryan Cantrill, Co-fundador e CTO da Oxide Computer Avi Kivity, criador da KVM, co-fundador e CTO da ScyllaDB Liz Rice, diretora de código aberto com especialistas do eBPF Isovalent Andy Pavlo, professor da CMU Ashley Williams, fundadora/CEO da Axo, antiga equipe de núcleo da Rust, fundadora da Fundação Rust Carl Lerche, criador de Tóquio, colaborador do Rust e engenheiro da AWS Inscreva-se agora – é grátis Inscreva-se agora – é grátis Inscreva-se agora – é grátis Além de outra grande conversa de Ivan do ShareChat, espere mais de 60 conversas de engenharia sobre otimização de desempenho na Disney/Hulu, Shopify, Lyft, Uber, Netflix, American Express, Datadog, Grafana, LinkedIn, Google, Oracle, Redis, AWS, ScyllaDB e mais. ShareChat: a principal plataforma de mídia social da Índia Para entender o alcance do desafio, é importante saber um pouco sobre o ShareChat, a principal plataforma de mídia social na Índia. No aplicativo ShareChat, os usuários descobrem e consomem conteúdo em mais de 15 idiomas diferentes, incluindo vídeos, imagens, músicas e muito mais. Entre os dois aplicativos, eles atendem a uma base de usuários em rápido crescimento que já tem mais de 325 milhões de usuários ativos mensais. Lojas de recursos de aprendizagem de máquina no ShareChat Esta história se concentra no sistema por trás das lojas de recursos ML para o aplicativo de vídeo de curta forma Moj. Ele oferece feeds totalmente personalizados para cerca de 20 milhões de usuários ativos diários, 100 milhões de usuários ativos mensais. Feeds atendem a 8.000 solicitações por segundo, e há uma média de 2.000 candidatos de conteúdo sendo classificados em cada solicitação (por exemplo, para encontrar os 10 melhores itens para recomendar). Ivan Burmistrov, engenheiro de software principal da ShareChat, explicou: “Nós calculamos recursos para diferentes ‘entidades’. Post é uma entidade, usuário é outro e assim por diante. Do ponto de vista computacional, eles são bastante semelhantes. No entanto, a diferença importante é o número de recursos que precisamos coletar para cada tipo de entidade. Quando um usuário solicita um feed, nós coletamos recursos de usuário para esse usuário único. No entanto, para classificar todas as postagens, precisamos coletar recursos para cada candidato (post) sendo classificado, então a carga total no sistema gerada por recursos de postagem é muito maior do que a gerada por recursos de usuário. Esta diferença desempenha um papel importante em nossa história.” O que deu errado No início, o foco principal era construir uma loja de recursos do usuário em tempo real porque, naquele momento, os recursos do usuário eram mais importantes.A equipe começou a construir a loja de recursos com esse objetivo em mente.Mas então as prioridades mudaram e os recursos do post também se tornaram o foco.Esta mudança aconteceu porque a equipe começou a construir um sistema de classificação completamente novo com duas grandes diferenças em relação ao seu antecessor: Posts em tempo real são mais importantes Número de postos a serem classificados aumentou de centenas para milhares Ivan explicou: “Quando fomos testar este novo sistema, falhou miseravelmente. Com cerca de 1 milhão de características por segundo, o sistema ficou sem resposta, latências passaram pelo telhado e assim por diante.” Em última análise, o problema veio de como a arquitetura do sistema usou recipientes de dados pré-agregados chamados de telhas. Por exemplo, eles podem agregar o número de gostos para uma postagem em um minuto dado ou outro intervalo de tempo. Isso permite que eles calculem métricas como o número de gostos para várias postagens nas últimas duas horas. Aqui está uma visão de alto nível da arquitetura do sistema. Há alguns tópicos em tempo real com dados brutos (gostos, cliques, etc.). Um trabalho do Flink os agrega em telhas e os escreve para o ScyllaDB. Então há um serviço de recursos que solicita telhas do ScyllaDB, os agrega e retorna os resultados ao serviço de feed. O esquema inicial de banco de dados e a configuração de telhado levaram a problemas de escalabilidade. Originalmente, cada entidade tinha sua própria partição, com linhas timestamp e nome de recurso sendo ordenadas colunas de agrupamento. ]. As telhas foram calculadas para segmentos de um minuto, 30 minutos e um dia. Querer uma hora, um dia, sete dias ou 30 dias exigia recolher cerca de 70 telhas por recurso em média. Saiba mais nesta masterclass de modelagem de dados NoSQL Se você fizer a matemática, fica claro por que falhou. O sistema precisava lidar com cerca de 22 bilhões de linhas por segundo. No entanto, a capacidade do banco de dados era de apenas 10 milhões de linhas por segundo. Otimização inicial Naquele momento, a equipe foi em uma missão de otimização. O esquema de banco de dados inicial foi atualizado para armazenar todas as linhas de recursos juntas, serializadas como buffers de protocolo para um determinado timestamp. Como a arquitetura já estava usando o Apache Flink, a transição para o novo esquema de telhado foi bastante fácil, graças às capacidades avançadas do Flink na construção de tubulações de dados. Com esta otimização, o multiplicador "Features" foi removido da equação acima, e o número de linhas necessárias para recuperar foi reduzido por 100X: de cerca de 2 bilhões a 200 milhões de linhas/sec. A equipe também otimizou a configuração da telha, adicionando telhas adicionais por cinco minutos, três horas e cinco dias a telhas de um minuto, 30 minutos e um dia. Para lidar com mais linhas/segundo no lado do banco de dados, eles mudaram a estratégia de compactação do ScyllaDB de incremental para nivelado. Essa opção se adequava melhor aos padrões de consulta, mantendo as linhas relevantes juntas e reduzindo a leitura I/O. O resultado: a capacidade do ScyllaDB foi efetivamente duplicada. Saiba mais sobre estratégias de compactação A maneira mais fácil de acomodar a carga restante teria sido escalar o ScyllaDB 4x. No entanto, aglomerados mais / maiores aumentariam os custos e isso simplesmente não estava em seu orçamento. Melhoria da localização do cache Uma maneira potencial de reduzir a carga no ScyllaDB foi melhorar a taxa de impacto do cache local, então a equipe decidiu pesquisar como isso poderia ser alcançado. A escolha óbvia foi usar uma abordagem de hashing consistente, uma técnica bem conhecida para direcionar uma solicitação a uma determinada réplica do cliente com base em algumas informações sobre a solicitação. Uma vez que a equipe estava usando NGINX Ingress em sua configuração do Kubernetes, usar as capacidades do NGINX para hashing consistente parecia uma escolha natural. Por documentação do NGINX Ingress, configurar hashing consistente seria tão simples quanto adicionar três linhas de código. Esta configuração simples não funcionou. especificamente: O subconjunto do cliente levou a um enorme remaping de chaves – até 100% no pior caso. Uma vez que as chaves dos nós podem ser alteradas em um anel de hash, era impossível usar cenários da vida real com autoescalagem. Foi difícil fornecer um valor de hash para uma solicitação porque a Ingress não suporta a solução mais óbvia: um cabeçalho gRPC. A latência sofreu grave degradação, e não era claro o que estava causando a latência da cauda. Para apoiar um subconjunto dos pods, a equipe modificou sua abordagem. Eles criaram uma função de hash de dois passos: primeiro hashing uma entidade, depois adicionando um prefixo aleatório. Isso distribuiu a entidade pelo número desejado de pods. Em teoria, esta abordagem poderia causar uma colisão quando uma entidade é mapeada para o mesmo pod várias vezes. Ingress não suporta o uso do cabeçalho gRPC como uma variável, mas a equipe encontrou uma solução: usando a reescrita do caminho e fornecendo a chave de hash necessária no próprio caminho. Infelizmente, identificar a causa da degradação da latência teria levado um tempo considerável, bem como melhorias na observabilidade. Para cumprir o prazo, a equipe dividiu o serviço Feature em 27 serviços diferentes e dividiu manualmente todas as entidades entre eles no cliente. Não foi a abordagem mais elegante, mas, foi simples e prático – e alcançou ótimos resultados. A taxa de hit do cache melhorou para 95% e a carga do ScyllaDB foi reduzida para 18,4 milhões de linhas por segundo. Com este design, o ShareChat escalou sua loja de recursos para 1B recursos por segundo até março. No entanto, essa abordagem de divisão de implantação da “velha escola” ainda não era o design ideal. Manter 27 implantações era tedioso e ineficiente.Além disso, a taxa de impacto do cache não era estável e a escalação era limitada por ter que manter uma alta contagem mínima de podes em cada implantação. A próxima fase de otimização: hashing consistente, serviço de recursos Preparado para mais uma rodada de otimização, a equipe revisitou a abordagem de hashing consistente usando um sidecar, chamado Envoy Proxy, implementado com o serviço de recursos. Envoy Proxy forneceu melhor observabilidade que ajudou a identificar o problema da cauda de latência. O problema: padrões de solicitação diferentes para o serviço de recursos causaram uma enorme carga na camada e no cache do gRPC. A equipe então otimizou o serviço Feature. Forjou a biblioteca de cache (FastCache da VictoriaMetrics) e implementou batch writes e melhor expulsão para reduzir a contenção do mutex em 100x. Forjado gprc-go e implementado buffer pool em diferentes conexões para evitar contenção durante o alto paralelismo. Usou o agrupamento de objetos e os parâmetros do coletor de lixo ajustado (GC) para reduzir as taxas de alocação e os ciclos de GC. Com o Envoy Proxy lidando com 15% do tráfego em sua prova de conceito, os resultados foram promissores: uma taxa de hit de cache de 98%, o que reduziu a carga no ScyllaDB para 7,4M linhas/segundo. Lições aprendidas Aqui está o que essa viagem parecia a partir de uma perspectiva de linha do tempo: Para concluir, Andrei resumiu as principais lições aprendidas pela equipe deste projeto (até agora): Mesmo quando a equipe do ShareChat mudou drasticamente o design do sistema, ScyllaDB, Apache Flink e VictoriaMetrics continuaram a funcionar bem. Cada otimização é mais difícil do que a anterior – e tem menos impacto. Soluções simples e práticas (como dividir a loja de recursos em 27 implementações) realmente funcionam. A solução que oferece o melhor desempenho nem sempre é fácil de usar. Por exemplo, seu esquema de banco de dados revisto traz um bom desempenho, mas é difícil de manter e entender. Cada sistema é único.Às vezes, você pode precisar forcar uma biblioteca padrão e ajustá-la para o seu sistema específico para obter o melhor desempenho. Assista ao P99 CONF! Assista ao P99 CONF! Assista ao P99 CONF! Sobre Cynthia Dunlop Cynthia é Diretora Sênior de Estratégia de Conteúdo na ScyllaDB. Ela escreve sobre desenvolvimento de software e engenharia de qualidade há mais de 20 anos.