No início de dezembro, o NeurIPS 2020 foi executado em Gather. Para se preparar, a equipe de infraestrutura executou uma série de testes de carga simulados. Durante o processo, nós:
- Quebramos e consertamos nossa infra-estrutura (muitas vezes)
- Capacidade DigitalOcean esgotada em várias regiões
- Emitimos tantos certificados SSL que trocamos de fornecedor SSL
- Desmontou um equilibrador de carga DigitalOcean
- E mais...
Esta é a história dessa experiência.
Como chegamos até aqui
Gather é simultaneamente um aplicativo da Web moderno, um videogame multijogador e uma plataforma de bate-papo com vídeo ao vivo. Isso significa que nossa infraestrutura envolve vários componentes principais:
- Um serviço HTTP para servir o site e API
- Um serviço de jogo para lidar com atualizações do estado do jogo em tempo real
- Um serviço de vídeo para alimentar um chat de vídeo ao vivo
- Um banco de dados para a persistência
Queríamos encontrar a maneira mais realista de simular o uso do Gather. Por isso, desenvolvemos um "teste de bot": usando um navegador básico sem cabeça e o Selenium, simulamos uma pessoa entrando em um espaço Gather andando aleatoriamente e usando um feed de áudio/vídeo pré-gravado em loop.
Anteriormente, realizávamos testes de carga desta forma com centenas de bots. Giravamos uma máquina de 2 vCPU/4GB por bot (a pilha WebRTC é bastante cara do lado do cliente [1]). Gerenciar algumas centenas de máquinas estava bem desta forma com a ajuda de alguns scripts.
Desta vez, quisemos simular 10.000-15.000 participantes simultâneos. Por isso, recorremos à Kubernetes.
Arquitetura
General Kubernetes vocab: um cluster consiste em nós (VMs). Cada nó pode rodar uma ou mais cápsulas. Cada cápsula pode rodar um ou mais recipientes. As cápsulas atuam como "hospedeiros lógicos" para containers - containers dentro da mesma cápsula podem falar uns com os outros via localhost.
No nosso caso, cada pod contém dois contêineres: gathertown/gather -town-loadt-test e gathertown/standalone-chrome.
- gathertown/gather -town-loadtest é baseado no nó imagem base. Ele contém e executa nosso código JavaScript, implementado usando o Selenium.
- O Collecttown/standalone-chrome é baseado no selênio/crômio-padronizado imagem de base. Ela contém nossos arquivos de vídeo e áudio, e roda um servidor Selenium Chrome.
gathertown/gather -town-loadtest se comunica com gathertown/standalone-chrome pelo localhost enquanto executa o teste de carga. gathertown/standalone-chrome executa o navegador e faz as solicitações para Gather.
Operando o teste de carga
Com base em nossas projeções, esse teste de carga custaria cerca de US$ 400 a US$ 600 por hora, dependendo da máquina base usada. O plano era usar 20 clusters do Kubernetes e apontar cada cluster para um espaço diferente do Gather , simulando a carga em 20 salas diferentes de sessões de pôsteres simultâneas do NeurIPS, com 500 bots por sala - simulando, no geral, 10 mil usuários simultâneos no site.
No total, fizemos três testes de carga:
- 10k bots usando 21.760 vCPUs em 20 clusters (680 VMs com 32 vCPUs cada)
- 10k bots usando 26.720 vCPUs em 20 clusters (3.340 VMs com 8 vCPUs cada)
- 15k bots usando 40.080 vCPUs em 30 clusters (5.010 VMs com 8 vCPUs cada)
O tipo de nó 32 vCPU é na verdade mais caro porque são vCPUs dedicadas (em comparação com os 8 nós de vCPU que são vCPUs compartilhadas). Ver nota de rodapé [2] que explica a escolha do tamanho do nó.
Excesso de capacidade do centro de dados
Com toda a preparação completa, estávamos prontos para escalar para nosso primeiro teste. Começamos solicitando 20 clusters de Kubernetes (34 nós por cluster, 32 nós vCPU) em nossa região padrão. Os primeiros vários clusters foram provisionados sem nenhum problema. Mas então, as coisas começaram a desacelerar. O que estava acontecendo?
Olhamos mais de perto para os agrupamentos que estavam demorando uma eternidade para fornecer e notamos algo interessante - um aviso dizendo "não forneceu..." para alguns dos nós. Pensando que não havia como esgotar a capacidade de uma região, tentamos fazer o provisionamento em outra região. Eis que o provisionamento em outra região funcionou... até que, mais uma vez, não conseguimos fazer o provisionamento.
Este padrão continuou ao longo de várias horas enquanto nos esforçávamos para fornecer recursos suficientes para o teste de carga. Finalmente, uma vez distribuídos por muitas regiões de centros de dados em todo o mundo, atingimos a capacidade.
Será que estamos aparecendo em algum painel interno da DigitalOcean agora mesmo?
Com certeza, nós estávamos.
O e-mail
Na manhã seguinte recebemos um e-mail da DigitalOcean, sugerindo que esperássemos uma chamada para falar sobre nosso caso de uso. Nosso representante nos explicou,
Cheguei até a equipe e perguntei se poderíamos sugerir qualquer outra região globalmente para a qual pudéssemos redirecionar você e sua equipe, mas acontece que a quantidade de clusters que você está tentando criar excede a capacidade em qualquer região.
Por isso, agendamos uma ligação para o dia seguinte. Fomos recebidos não por um mas por dois representantes da DigitalOcean [3] que passaram uma hora, gratuitamente, mergulhando em nossos casos completos de uso atual e futuro da Kubernetes. Afinal de contas, não estávamos apenas usando Kubernetes para este teste de carga - já estávamos trabalhando na mudança de toda nossa implantação para Kubernetes.
Aprendemos muito com esta chamada, e não poderia ter sido mais tranquilizador para nosso futuro uso da DigitalOcean Kubernetes. Nem uma única pedra foi deixada por virar enquanto discutíamos os acertos que encontramos. Um dos representantes até se ofereceu para saltar em uma chamada conosco ao vivo enquanto realizávamos nosso próximo teste de carga, para nos direcionar para as melhores regiões a serem usadas naquele momento. Aceitamos essa oferta.
Vela suave
Marcamos uma chamada de 30 minutos com nosso representante, que recomendou um pequeno número de centros de dados chave com capacidade suficiente. Fizemos a solicitação de 20 clusters (desta vez 167 nós por cluster, 8 nós de vCPUs). Ao final dos 30 minutos, estávamos todos prontos para partir, com 26.720 vCPUs sob nossa ponta dos dedos!
Armados com o conhecimento das melhores regiões para usar, quando chegou a hora de nosso terceiro e último teste de carga, nós escalamos para as 40k vCPUs completas por conta própria, sem nenhum problema!
Destruindo nosso sistema de vídeo
O Roteador
Enquanto realizávamos nosso primeiro teste de carga, nós quebramos nossa infra-estrutura de vídeo de encenação. Ninguém em nosso escritório (nós, comida de cachorro na encenação) foi capaz de se conectar a qualquer outra pessoa. O culpado: nossa réplica única de implantação do roteador de vídeo.
O roteador de vídeo é um serviço WebSocket que funciona como uma espécie de "lista telefônica" virtual. Quando Alice se aproximar de Bob em Gather, o vídeo de Alice se conectará automaticamente a Bob (e vice-versa). Para isso, Alice precisa saber a qual servidor de vídeo Bob está conectado no momento para que ela também possa se conectar a esse servidor de vídeo. Para descobrir isso, ela entra em contato com o roteador por meio de um WebSocket.
Quando o roteador falha, todo o sistema de vídeo falha. Ninguém mais pode conectar-se entre si. Sem mencionar a fraqueza de projeto de um ponto singular de falha global, o roteador singular simplesmente não conseguia lidar com a carga.
Então, nos propusemos a replicar o roteador.
Capacidade do Servidor de Vídeo e Limite da Taxa de Criptografia
Enquanto isso, descobrimos também que nos faltavam servidores de vídeo suficientes para lidar com a carga. Enquanto provisionávamos centenas de servidores de vídeo a mais (para receber carga de qualquer região do mundo), encontramos o limite de 50 novos certificados/semanas da Let's Encrypt, e trocamos os provedores SSL na hora.
Derrubando um Balanceador de Carga DigitalOcean
Com o roteador replicado, servidores de vídeo provisionados e conectados com certificados SSL, estávamos prontos para outro teste de carga! Dentro de 30 minutos, criamos 26.720 vCPUs e começamos a martelar o site.
O sistema de vídeo foi abaixo, novamente. Hmm...
Enquanto investigávamos mais a fundo, notamos algo peculiar: embora o roteador estivesse em baixo, os servidores de roteadores individuais que o suportavam não estavam.
Nosso novo roteador replicado apresentava um balanceador de carga DigitalOcean na frente de várias réplicas de roteadores. Após algumas escavações adicionais, observamos que o balanceador de carga retornou um erro da série 500 ou falhou completamente em responder às solicitações quando excedemos ~6k conexões WebSocket simultâneas. Enquanto isso, os servidores de roteadores individuais foram perfeitamente responsivos e saudáveis. O balanceador de carga em si foi o gargalo de estrangulamento.
Era uma sexta-feira às 20:40h e decidimos enviar um e-mail a nossos representantes da DigitalOcean. Às 22h06 recebemos a seguinte resposta:
Na verdade, estamos em beta fechada para dois novos tamanhos de Balanceador de Carga, então, amanhã (ou certamente até segunda-feira), eu o adicionarei a esse grupo. Isso lhe dará acesso a um tamanho LB grande e carnudo que deverá lhe dar muito mais com que trabalhar.
Desde que escreveu este posto, a DigitalOcean lançou vários tamanhos de balanceadores de carga para o público.
40k vCPUs Hammering Prod
Neste ponto, já estávamos em dois testes de carga, nos preparando para nosso terceiro e último teste de carga. Fornecemos a capacidade de servidor de vídeo que precisávamos, duplicamos nosso roteador [4], e estávamos prontos para dar ao nosso local de produção um teste de carga adequado.
Nosso vídeo Algoritmo de balanceamento de carga atinge uma caixa de borda
Não muito tempo para martelar nossa infra-estrutura de produção ao vivo, um dos engenheiros chamou "STOP STOP". Algo estava indo totalmente para o lado com nossa infra-estrutura de vídeo.
Descobrimos que um de nossos servidores de vídeo estava em um estado anormal, e assim relatando estatísticas de carga incomuns. Enquanto isso, o roteador pensou que o servidor estava levemente carregado. Mas à medida que mais e mais pessoas se ligavam ao servidor de vídeo com defeito, o algoritmo de roteamento falhou em considerar que o servidor de vídeo não estava realmente encaminhando o vídeo de ninguém. O único servidor era um buraco negro sugando novas conexões de vídeo para dentro dele e falhando em encaminhar as conexões de vídeo para fora.
Paramos rapidamente o teste de carga, desativamos o servidor de vídeo com defeito, e começamos a repensar nosso algoritmo de roteamento. Após uma hora de reflexão, implementamos uma correção rápida na lógica de roteamento e tentamos novamente.
O teste de carga completa
Após a implantação do novo algoritmo de roteamento e a reativação de nosso servidor de vídeo defeituoso, as coisas pareciam boas para outro teste de carga. Decidimos tentar os seguintes passos para ter certeza de que nosso site estava pronto:
- Ligar e desligar de repente 10k bots para ver o que acontece com um súbito aumento do tráfego
- Tensão prolongada (1h+) abaixo de 15k bots para ver se algo se decompõe a longo prazo
E tudo funcionou! 🚀
Takeaways
Há muitas maneiras de testar a carga, mas descobrimos que este teste de carga não deixou dúvidas em nossas mentes sobre se poderíamos suportar o pico de carga no NeurIPS 2020. Enfatizamos os limites não apenas de nossa própria infra-estrutura, mas também da DigitalOcean. Ficamos impressionados com o nível de suporte gratuito que recebemos durante todo o processo e continuamos a ser clientes extremamente felizes.
Finalmente, estamos contratando! Se aventuras como estas o deixam entusiasmado, adoraríamos ouvir de você.
Notas de rodapé
- Enraivecido, mas sabe como fazer isso melhor? Estamos à procura de Engenheiros de Software em Sistemas de Vídeo!
- Em testes iniciais, notamos que o nó mestre se desconectava (e se tornava inalcançável via kubectl) durante grandes eventos de escala de cluster. Depois de algum retrocesso inicial com o apoio da DigitalOcean, nós (erroneamente) chegamos a entender que nós maiores ajudariam. Afinal, haveria menos nós no cluster para o mestre lidar e (esta parte seguinte foi um erro) achamos que o nó mestre tinha o mesmo tamanho que o tamanho do nó especificado. Acontece que o nó é inteiramente gerenciado pela DigitalOcean e cresce/rugas dependendo do tamanho do aglomerado. A chave era fornecer um grande aglomerado na frente para evitar que o mestre redimensionasse na hora (causando desconexões) - e não importava se usássemos tipos de nós grandes ou pequenos. Assim, escolhemos o maior (para eficiência da embalagem do módulo) tipo de nó de CPU compartilhada, que era mais econômico que as variantes de CPU dedicada (veja gráficos acima).
- Muito obrigado aos nossos incríveis representantes da DigitalOcean e a Darian Wilkin e David Gonzalez por estarem lá por nós!
- Agora, verdade seja dita, nós não acabamos usando os tipos maiores de LB. Atingimos outro problema: o equilibrador de carga estava mascarando o IP do usuário, que usamos para a atribuição geográfica do servidor de vídeo. Em vez de resolver isto e contar com um LB beta, nós fomos para uma solução simples: atribuição de roteador de vídeo aleatório, do lado do cliente. Estávamos confiantes de que tínhamos superprovisionado o roteador, portanto não tínhamos que nos preocupar em descobrir novos roteadores ou em potencialmente redirecionar o código do cliente.