Imagine procurar um voo em um site de viagens e aguardar 10 segundos à medida que os resultados carregam. Parece uma eternidade, certo? As plataformas de pesquisa de viagens modernas devem retornar os resultados quase instantaneamente, mesmo sob carga pesada. No entanto, não faz muito tempo, a API do nosso mecanismo de busca de viagem teve uma latência em P95 em torno de 10 segundos. Isso significava 5% das pesquisas do usuário, geralmente durante o pico de tráfego, levavam 10 segundos ou mais. Resultado – usuários frustrados, taxa de rejeição pesada e pior – perdeu vendas. A redução de latência, portanto, é não negociável nesses casos.
Este artigo é um estudo de caso do mundo real de como evoluímos nossa infraestrutura em nuvem para matar o dragão de latência. Aproveitando o Google Cloud Run for Scalable Compute e Redis Para armazenamento em cache inteligente, transformamos a latência P95 da nossa API de pesquisa de ~ 10 segundos para ~ 2 segundos. Aqui, percorreremos todo esse processo de redução de latência. Isso inclui os gargalos de desempenho, as otimizações e as melhorias dramáticas que eles trouxeram.
O gargalo de latência
A latência alta foi um problema sério. Aproveitando -se profundamente, encontramos vários culpados arrastando nossos tempos de resposta. Tudo isso teve um fator comum – eles fizeram nossa API de pesquisa fazer muito levantamento pesado em cada solicitação. Antes de conseguirmos uma redução geral de latência, aqui estão os problemas que tivemos que corrigir:

- Várias chamadas de back -end: Para cada consulta de usuário, o serviço entrou em contato com vários serviços a jusante (provedores de tarifas da companhia aérea, bancos de dados para informações auxiliares, etc.) sequencialmente. Por exemplo, a pesquisa de um voo chamou três APIs diferentes às vezes, cada uma levando cerca de 2 a 3 segundos. A latência combinada empilhou cerca de 10 segundos para algumas pesquisas.
- Sem camada de cache: Como não havia cache de memória ou redis para retornar rapidamente resultados recentes, todas as solicitações no site começaram do zero. Até pesquisas idênticas foram repetidas em poucos minutos. Isso significava que até rotas populares ou dados estáticos (como detalhes do aeroporto) eram buscados no banco de dados ou terceiros todas as vezes.
- Cloud Run Fria começa: Nosso serviço foi executado no Cloud Run (uma plataforma de contêiner sem servidor). Com as configurações padrão (0 instâncias mínimas), quando o tráfego estava ocioso e uma nova solicitação chegou, a Cloud Run teve que aumentar uma instância do contêiner. Essas partidas frias adicionaram um atraso significativo (geralmente 1 a 2 segundos de sobrecarga). Esse “imposto de inicialização” estava prejudicando profundamente nossa latência de cauda.
- Solicitações únicas: Inicialmente, configuramos cada contêiner em nuvem para lidar com apenas uma solicitação por vez (simultaneidade = 1). Esse manuseio simplificado de solicitações, mas significava que uma explosão de 10 pesquisas simultâneas aumentaria imediatamente 10 instâncias separadas. Com partidas frias e CPU limitado por instância, nosso sistema lutou para lidar com picos com eficiência.
Todos esses fatores formaram uma tempestade perfeita para a latência lenta do P95. Sob o capô, nossa arquitetura simplesmente não foi otimizada para a velocidade. As consultas estavam fazendo um trabalho redundante e nossa infraestrutura não foi ajustada para uma carga de trabalho sensível à latência. As boas notícias? Cada gargalo foi uma oportunidade para a redução de latência.
Leia também: Cloud Run como uma plataforma sem servidor para implantar contêineres
O que mudamos para redução de latência
Tornamos a redução de latência em duas frentes principais. Armazenamento em cache para evitar o trabalho repetitivo e otimizações de execução em nuvem para minimizar o início do frio e o processamento da sobrecarga. Aqui está como o back -end evoluiu:
Introduziu uma camada de cache redis
Implantamos um cache redis para operações caras de curto-circuito em caminhos quentes. A idéia era bem direta: armazenar os resultados de consultas frequentes ou recentes e servir as solicitações subsequentes diretamente. Por exemplo, quando um usuário pesquisou voos de Nova York para LON por determinadas datas, nossa API buscava e compilava os resultados uma vez. Em seguida, cache essa “resposta da tarifa” em Redis por um curto período.
Se outro usuário (ou o mesmo usuário) fizesse a mesma pesquisa logo após, o back -end poderá retornar os dados da tarifa em cache em milissegundos, evitando chamadas repetidas para as APIs externas e consultas de banco de dados. Ao evitar chamadas caras a montante nos hits de cache, reduzimos drasticamente a latência para consultas quentes.
Aplicamos o armazenamento em cache a outros dados também, como dados de referência estáticos ou de mudança lenta. Por exemplo, códigos do aeroporto, metadados da cidade, taxas de câmbio, agora usavam cache. Em vez de atingir nosso banco de dados para obter informações do aeroporto em cada solicitação, o serviço agora o recupera do Redis (preenchido na inicialização ou no primeiro uso). Isso reduziu muitas pesquisas menores que estavam adicionando milissegundos aqui e ali (que se somam sob carga).
Em armazenamento em cache
Como regra geral, decidimos “cache o que é quente”. Rotas populares, preços recentemente buscados e dados de referência estáticos como informações do aeroporto foram mantidas prontamente disponíveis na memória. Para manter os dados em cache frescos (importantes onde os preços mudam), definimos vencimentos sensíveis ao TTL (tempo de vida) e regras de invalidação. Por exemplo, os resultados da pesquisa de tarifas foram armazenados em cache por alguns minutos no máximo.
Depois disso, eles expirariam, para que novas pesquisas possam obter preços atualizados. Para dados altamente voláteis, poderíamos até invalidar proativamente as entradas de cache quando detectamos alterações. Como observam os documentos de Redis, os preços dos voos geralmente atualizam apenas “a cada poucas horas”. Portanto, um TTL curto combinado com a invalidação baseada em eventos equilibra a frescura vs. a velocidade.
O resultado? Nos acertos de cache, o tempo de resposta por consulta caiu de vários segundos para algumas centenas de milissegundos ou menos. Tudo isso foi graças a Redis, que pode servir dados incrivelmente rápidos sobre a memória. De fato, os relatórios da indústria mostram que o uso de um “cache de tarifa” na memória pode transformar uma consulta de voo de vários segundos em uma resposta em apenas dezenas de milissegundos. Embora nossos resultados não tenham sido tão instantâneos em geral, essa camada de cache deu um grande impulso. Redução significativa de latência foi alcançada, especialmente para pesquisas repetidas e consultas populares.
Configurações otimizadas de execução da nuvem para redução de latência
O armazenamento em cache ajudou no trabalho repetido, mas também precisávamos otimizar o desempenho para consultas e escalas iniciantes. Portanto, ajustamos nosso serviço de execução em nuvem para baixa latência.
Sempre uma instância quente
Ativamos instâncias mínimas = 1 para o serviço de execução em nuvem. Isso garantiu que pelo menos um contêiner está pronto para receber solicitações, mesmo durante os períodos ociosos. A primeira solicitação do usuário não incorre mais em uma penalidade de partida a frio. Os engenheiros do Google observam que manter uma instância mínima pode melhorar drasticamente o desempenho para aplicativos sensíveis à latência, eliminando o atraso de inicialização zero para um.
No nosso caso, definir instâncias min para 1 (e até 2 ou 3 durante o horário de pico) significava que os usuários não estavam presos esperando que os contêineres girassem. A latência do P95 viu uma queda significativa apenas dessa otimização.
Aumento da concorrência por contêiner
Revisitamos nossa configuração de simultaneidade. Depois de garantir que nosso código pudesse lidar com solicitações paralelas com segurança, aumentamos a concorrência da nuvem de 1 para um número maior. Experimentamos valores como 5, 10 e, eventualmente, resolvemos 5 para nossa carga de trabalho. Isso significava que cada contêiner poderia lidar com até 5 pesquisas simultâneas antes que uma nova instância necessária para iniciar.
Resultado – menos novas instâncias geradas durante picos de trânsito. Isso, por sua vez, significava menos partidas frias e menos sobrecarga. Essencialmente, deixamos cada contêiner fazer um pouco mais de trabalho em paralelo, até o ponto em que o uso da CPU ainda era saudável. Monitoramos de perto a CPU e a memória – nosso objetivo era usar cada instância com eficiência sem sobrecarregá -la.
Esse ajuste ajudou a suavizar a latência durante as explosões: se 10 solicitações chegassem imediatamente, em vez de 10 partidas frias (com concorrência = 1), lidaríamos com eles com 2 instâncias quentes lidando com 5 cada, mantendo as coisas rápidas.
Startup e processamento mais rápido
Também fizemos alguns ajustes no nível do aplicativo para iniciar mais rapidamente e executar mais rápido na nuvem. Além disso, ativamos o recurso Startup CPU Boost da Cloud Run, que fornece uma explosão de CPU a novas instâncias durante a inicialização. Também usamos uma imagem de contêiner de base fina e carregamos apenas módulos essenciais na inicialização.
Certas etapas de inicialização (como carregar arquivos de configuração grandes ou aquecer certos caches) também foram movidos para a fase de inicialização do contêiner, em vez de no horário da solicitação. Graças às instâncias de Min, essa startup ocorreu com pouca frequência. Na prática, quando chegou uma solicitação, a instância já estava inicializada (conexões de banco de dados abertas, carregadas de configuração etc.), para que ela pudesse começar a processar a consulta imediatamente.
Pagamos essencialmente o custo de inicialização uma vez e reutilizamos em muitas solicitações, em vez de pagar um pouco desse custo em cada solicitação.
Os resultados foram instantaneamente visíveis com essas otimizações em vigor. Monitoramos o desempenho da nossa API antes vs. depois. A latência do P95 despencou de aproximadamente 10 segundos, até cerca de 2 segundos. Esta foi uma experiência de carregamento 5 vezes mais rápida para nossos usuários. Mesmo a latência média melhorou (para consultas de acumulação de cache, muitas vezes eram <500 ms).
Mais importante, as respostas se tornaram consistentes e confiáveis. Os usuários não experimentaram mais as dolorosas e muito dicas de espera de 10 segundos. O sistema poderia lidar com picos de tráfego graciosamente: a nuvem executada em instâncias adicionais quando necessário. Com recipientes quentes e maior simultaneidade, o fez sem sufocar em partidas frias.
Enquanto isso, o cache do Redis absorveu consultas repetidas e a carga reduzida em nossas APIs e bancos de dados a jusante. Isso também melhorou indiretamente a latência, impedindo que esses sistemas se tornassem gargalos.
O efeito líquido foi uma API de pesquisa mais escalável e mais escalável que acompanhou as expectativas de respostas rápidas de nossos clientes e uma experiência suave.
Takeaways -chave
De todo o conjunto de otimizações que realizamos para redução de latência, aqui estão as principais conclusões e pontos importantes para você considerar.
- Medir e alvo Latência da cauda: O foco na latência p95 (e acima) é crucial. Ele destaca os piores atrasos que os usuários reais sentem. A redução da latência do percentil 95 de 10 para 2s tornou nossas piores experiências 5 vezes melhor, mais rápido. Uma grande vitória para a satisfação do usuário! Portanto, sempre monitore essas métricas de alto percentil, não apenas a média.
- Use o cache para evitar trabalhos redundantes: A introdução de um cache do Redis provou ser um divisor de águas para nós. O armazenamento em cache freqüentemente solicitou dados reduzem drasticamente os tempos de resposta, atendendo aos resultados da memória. A combinação de velocidade na memória e invalidação atenciosa (usando TTLs e atualizações) pode descarregar cálculos caros do seu back-end.
- Otimize o servidor sem velocidade: A Cloud Run nos deu uma escala fácil, mas, para torná-la realmente de baixa latência, aproveitamos seus recursos notáveis-mantendo as instâncias mínimas quentes. Isso eliminou o atraso de partida fria e a simultaneidade e os recursos sintonizados para que os casos sejam usados com eficiência sem ficar sobrecarregados. Um pouco de custo inicial (instância sempre ativa) pode valer a pena a recompensa em desempenho consistente.
- Paralelize e simplificar sempre que possível: Reexaminamos nosso fluxo de solicitação para remover a serialização e atrasos desnecessários. Ao fazer coisas como chamadas externas paralelas e fazer uma configuração única durante a inicialização (não para todas as solicitações), raspamos segundos do caminho crítico. Cada micro otimização (E/S não bloqueador, código mais rápido, dados de pré-carregamento) se resume em um sistema distribuído de alta escala.
- Perfil e iteração contínuos: Por fim, uma coisa importante a ser observada aqui é que essa foi uma jornada iterativa. Usamos o monitoramento e o perfil para encontrar os maiores gargalos, abordamos um por um e medimos o impacto. O ajuste de desempenho raramente é um. Trata-se de melhorias orientadas a dados e às vezes correções criativas para atingir suas metas de latência.
Conclusão
Embora essas estratégias de redução de latência possam parecer demais para lidar ao mesmo tempo, uma verificação sistemática através de cada uma é bastante suave na prática. A mais incomparável para todo o exercício é que transformamos nossa API de busca de viagens de uma experiência lenta em uma que parece instantânea. Em um mundo em que os usuários esperam respostas “ontem”, cortar a latência do P95 de 10s para 2s fez toda a diferença no fornecimento de uma experiência de pesquisa de viagem suave.
Faça login para continuar lendo e desfrutar de conteúdo com curado especialista.
Leave a Reply