Como melhoramos o desempenho do Moove em 59%
A maioria dos usuários do Moove está longe dos nossos servidores. Veja como reduzimos em 59% o tempo até a primeira resposta sem mover nenhum servidor.
A distância era a nossa dependência mais lenta
A maioria das pessoas que abre o Moove app está em redes móveis na Nigéria e na Indonésia, a milhares de quilômetros dos servidores que respondem a elas. Neste mês, mudamos a forma como as requisições dessas pessoas chegam até nós, e o nosso tempo até o primeiro byte (TTFB), o intervalo entre o celular enviar uma requisição e a primeira parte da resposta chegar, caiu 59%, de 0,74 segundo para 0,31 segundo. Não movemos nenhum servidor para chegar a esse resultado.
Este texto explica por que a distância nos custou mais do que esperávamos, o que mudamos, a parte que se mostrou mais difícil do que a mudança em si e o que diríamos a outra equipe prestes a fazer o mesmo.
Por que uma viagem longa vira três
Os engenheiros chamam de latência o atraso entre enviar algo e esse algo chegar, e de tempo de ida e volta (RTT) o tempo que uma mensagem leva para chegar a um servidor e para a resposta voltar. De Lagos até os nossos servidores, o RTT fica entre 200 e 300 milissegundos. Isoladamente, isso é tolerável. O problema é que uma requisição raramente viaja sozinha.
Antes de um celular poder pedir qualquer coisa por uma conexão segura, ele precisa concluir dois handshakes, as trocas curtas de mensagens que estabelecem uma conexão. O handshake de TCP abre a conexão, e o handshake de TLS combina as chaves de criptografia que a mantêm privada. Cada um custa uma viagem de ida e volta completa. Só então a requisição propriamente dita é enviada, o que é a terceira viagem. A 250 milissegundos por viagem, o usuário espera cerca de três quartos de segundo antes de os nossos servidores terem feito qualquer trabalho.
Em redes móveis, esse custo é pago com muito mais frequência do que se imagina. Os celulares trocam de antena, os rádios entram em repouso para economizar bateria e os aplicativos vão para segundo plano. Qualquer um desses eventos pode derrubar a conexão, e a requisição seguinte precisa partir de uma conexão fria, na qual nenhum handshake foi feito ainda, e pagar de novo todo o custo de preparação. Uma conexão quente, já aberta e protegida, vai direto à requisição.
Não podíamos aproximar Lagos dos nossos servidores. Podíamos aproximar a preparação de Lagos.
Mudar o handshake de lugar, não os servidores
Uma rede de distribuição de conteúdo (CDN) costuma ser descrita como um cache: um conjunto de locais pelo mundo que guardam cópias de arquivos para serem servidos de perto. Nós a usamos por outro motivo. Para a nossa API, a interface com a qual o app conversa para fazer tudo o que faz, o cache fica totalmente desligado. Toda requisição continua viajando até a nossa origem, o nome que a CDN dá aos servidores que ficam atrás dela.
O que muda é o ponto onde a conexão começa. Agora, o celular abre a conexão com a localização de borda mais próxima, também chamada de ponto de presença (PoP): um dos locais da CDN espalhados pelo mundo, muitas vezes a dezenas de milissegundos de distância, e não a centenas. A borda faz a terminação de TLS, ou seja, a conexão segura com o celular termina ali. Em seguida, ela reutiliza um conjunto de conexões quentes com a nossa origem, mantidas abertas com keep-alive, uma configuração que deixa a conexão aberta entre requisições em vez de fechá-la. As duas viagens caras de preparação acontecem no trecho curto, e só a requisição em si atravessa o oceano, numa conexão que já está quente.
Três viagens longas viram uma viagem longa e duas curtas. Essa é toda a ideia.
Mantivemos o resto deliberadamente simples:
- Os clientes não perceberam nada. O app e o site mantiveram os mesmos endereços, então não foi preciso lançar nenhuma versão do app, e ninguém precisou atualizar nada.
- O comportamento em caso de falha continuou o mesmo. Os tempos limite são iguais aos de antes, então tudo o que falhava antes continua falhando da mesma forma. Não queríamos nenhum tipo novo de erro.
- A borda fecha as conexões ociosas primeiro. Toda conexão tem um tempo limite de inatividade, quanto tempo ela pode ficar sem uso antes de ser fechada. Se os nossos servidores fechassem uma conexão parada enquanto a borda ainda a considerasse aberta, a requisição seguinte falharia. Definir um tempo limite menor na borda garante que ela sempre desista primeiro e abra uma conexão nova.
- Protocolos mais novos para celulares. Os celulares podem se conectar por HTTP/3, a versão mais recente do protocolo de requisições da web. Ele roda sobre o QUIC, que reúne os dois handshakes em uma única viagem de ida e volta e lida melhor com a perda de pacotes, comum em redes móveis.
- Nenhuma região de fora. Os planos mais baratos de uma rede de borda costumam deixar de lado locais justamente nas partes do mundo onde estão os nossos usuários, então escolhemos a cobertura completa.
O que medimos
Medimos a partir de Sydney, que fica aproximadamente tão longe dos nossos servidores quanto Lagos, comparando o caminho antigo com o novo. Para cada um, abrimos cinco conexões frias novas, o caso que mais importa em redes móveis, e tomamos a mediana, também escrita como p50: o resultado do meio, que metade das requisições supera e metade não. O mesmo projeto também ativou a compressão dos scripts do nosso site, que eram enviados em tamanho integral. A compressão reduz um arquivo antes do envio, e o navegador o expande de novo na chegada; usamos o Brotli, um formato de compressão feito para a web.
| O que medimos | Antes | Depois | Melhoria |
|---|---|---|---|
| Handshakes de TCP e TLS concluídos (p50) | 490 ms | 59 ms | 88% mais rápido |
| Tempo até o primeiro byte (p50) | 743 ms | 307 ms | 59% mais rápido |
| Tamanho dos scripts da página inicial | 1,76 MB | 560 KB | 68% menor |
É no tempo até o primeiro byte que está o número de 59%. Uma conexão que levava meio segundo para ser preparada agora fica pronta em menos de um décimo disso, e uma primeira visita ao nosso site agora baixa dois terços a menos, em conexões nas quais muita gente paga por cada megabyte.
Esses números vêm de uma única cidade, não de um teste de referência global. Ainda assim, a conta vale para Lagos e Jacarta, e é lá que estão os nossos usuários.
A parte difícil era saber quem está chamando
Colocar algo na frente dos seus servidores é fácil. Manter as suas proteções funcionando atrás disso é onde foi a maior parte do esforço.
Como a maioria das plataformas de pagamento, o Moove usa limitação de taxa, um teto para quantas requisições cada chamador pode fazer em um determinado período, e esses limites são aplicados por endereço IP do chamador, o número que identifica um dispositivo na internet. Atrás de uma rede de borda, toda requisição chega com o endereço de uma localização de borda, e não com o do usuário. Se mais nada tivesse mudado, todos em Lagos dividiriam um único orçamento, e uma tarde movimentada começaria a barrar pessoas que não tinham feito nada de errado.
A solução se apoia em duas ideias.
Confie exatamente nas camadas que você tem. Uma CDN é um tipo de proxy reverso, um servidor que fica na frente do seu e repassa as requisições. Cada proxy acrescenta o endereço de onde recebeu a requisição a um cabeçalho padrão chamado X-Forwarded-For, de modo que o chamador real fica um número fixo de passos, ou saltos, atrás nessa lista. Errar esse número é um bug nos dois sentidos. Se você contar camadas de menos, todo usuário parece ser a sua localização de borda. Se contar camadas demais, você passa a confiar numa entrada que o próprio chamador escreveu. Por isso, fizemos desse número uma propriedade da infraestrutura, e não do código. Ele muda no mesmo instante em que a rede muda, e o nosso serviço se recusa a iniciar se o valor não fizer sentido, em vez de tentar adivinhar.
Deixe a borda atestar quem é o chamador e acredite somente na borda. Agora a borda grava o endereço do chamador real em cada requisição e sobrescreve qualquer coisa que o chamador tenha enviado no lugar dele. As nossas proteções só acreditam nesse valor quando conseguem verificar que a requisição realmente passou pela nossa borda. Todo limite que antes usava o endereço de rede agora usa o chamador real no tráfego vindo da borda. Acrescentamos essas regras ao lado das antigas em vez de alterá-las, de modo que cada mudança do lançamento era uma adição pura, que qualquer pessoa conseguia revisar em uma só leitura.
Um bug que pegamos antes de ir ao ar
Os Moove Profiles podem ficar no domínio próprio de um comerciante. As requisições para esses domínios passavam pelo nosso site com o domínio do comerciante anexado, para que o site soubesse qual perfil exibir.
A nova borda na frente do site remove esse tipo de informação das requisições recebidas, o que está absolutamente certo, porque um visitante nunca deveria poder defini-la. Isso também significava que, assim que o site passasse para trás da borda, todo domínio de comerciante mostraria discretamente a página inicial do Moove em vez do perfil do comerciante.
Pegamos isso na revisão, movemos primeiro o caminho dos domínios de comerciantes para uma rota própria e fizemos a nossa infraestrutura se recusar a aplicar a mudança do site em qualquer lugar onde essa transferência não tivesse acontecido. Um erro que antes só existia na cabeça de quem revisava agora é algo que a compilação impede.
A lição geral: ao adicionar uma camada que limpa entradas, procure tudo o que dependia das entradas que você está prestes a limpar.
Como fizemos o lançamento
Em infraestrutura de pagamentos não existe "publicar e ver o que acontece", então mudamos primeiro o ambiente de testes (staging) e depois a produção, e algumas escolhas mantiveram a virada sem sobressaltos. Reduzimos o TTL (tempo de vida) dos nossos registros de DNS, o catálogo da internet que transforma um nome como moove.xyz em um endereço de servidor. O TTL é por quanto tempo o resto da internet pode guardar essa resposta em cache, e o reduzimos de uma hora para cinco minutos, uma hora inteira antes de qualquer coisa mudar, de modo que a resposta antiga já tivesse expirado em todo lugar e uma reversão tivesse efeito em poucos minutos. Mantivemos os registros de endereço numa forma que podia ser trocada no lugar, porque removê-los e recriá-los teria deixado uma lacuna, e uma lacuna é uma indisponibilidade. Cada passo arriscado era uma pequena mudança isolada, com os passos para desfazê-la escritos em ordem.
O que diríamos a outra equipe
Se os seus usuários estão longe dos seus servidores, vale a pena ter uma rede de borda mesmo que ela não coloque nada em cache. Antes de fazer a virada:
- Liste tudo o que lê o endereço do chamador, incluindo limites de taxa, listas de permissão, sinais de fraude, registros de auditoria e regras de segurança. Cada um deles passa a ver o endereço da borda, a menos que você mude isso.
- Faça do número de camadas confiáveis parte da sua infraestrutura e recuse-se a iniciar quando ele estiver errado.
- Deixe a borda registrar o chamador real e confie nesse registro apenas no tráfego que você consegue verificar que veio da sua borda.
- Garanta que os seus servidores aceitem tráfego somente pela borda e observe antes de impor a regra.
- Se você coloca páginas em cache, monte a chave de cache, o conjunto de detalhes da requisição que um cache usa para decidir se duas requisições podem compartilhar uma mesma resposta armazenada, a partir de tudo em que a resposta realmente varia. Os servidores listam esses detalhes em um cabeçalho Vary, então leia-o em vez de confiar na memória.
- Meça antes e depois, e olhe o tempo de handshake e o tempo até o primeiro byte separadamente, para distinguir a preparação da conexão do tempo que os seus servidores gastam trabalhando.
Olhando para frente
A maioria das nossas páginas é personalizada, então o cache de borda do site fica quase ocioso por enquanto. A melhoria na conexão vale para todas as páginas, independentemente disso, e cada página agora pode optar por usar o cache quando for seguro compartilhá-la. Colocar respostas da API em cache exigiria um projeto e uma revisão próprios, e é a próxima coisa que queremos examinar.
O dinheiro deve se mover na velocidade da rede, não na velocidade da luz atravessando um oceano.
Se você desenvolve sobre o Moove, leia nossa introdução às APIs e SDKs de pagamentos Web3 e o Moove Developer Program.
Escrito pela equipe de engenharia do moove.xyz.
Sobre o moove.xyz
O moove.xyz é uma plataforma fintech Web3 global criada para a movimentação de valor sem permissão e sem esforço. Capacitamos empresas e consumidores em qualquer lugar a enviar, receber, fazer staking e trocar quaisquer criptomoedas entre quaisquer blockchains, tudo numa única plataforma.
Somos uma das primeiras empresas fintech Web3 do mundo a inovar e construir uma infraestrutura completa de pagamentos em cripto e finanças descentralizadas, com cobertura integrada e abrangente de acesso a carteiras multi-chain, handles de carteira personalizados, trocas de tokens cross-chain, transações cross-chain embutidas e uma rede financeira social descentralizada. Nossos principais produtos incluem Moove Profile, Moove Send, Moove Receive, Moove Stake, Moove Swap, Moove Rewards, Moove Discover e outros.
Nossa missão é simples: criar e distribuir tecnologia financeira sem permissão e sem esforço para o próximo 1 bilhão de usuários Web3. Acreditamos firmemente que o futuro da movimentação de dinheiro e valor será sem custo, sem fronteiras, sem permissão, sem esforço e feito para todos, e estamos construindo a melhor plataforma fintech Web3 para tornar esse futuro realidade.
Seu dinheiro. Sua jogada.






