Como eu saí de 100 queries/s à DB para 16/s utilizando Redis

O que Lerei?

Olá pessoal, meu nome é Luan, conhecido nas redes como Luxanna. Fiz esse post no Twitter um tempo atrás, mas acho que é um conteúdo muito bom, ainda mais pessoas como eu, iniciantes nesse mundo da programação. O que vou escrever aqui é uma adaptação do meu Tweet passado, trazendo uma visão refinada do que eu aprendi com o tempo, até mesmo para participar dessa comunidade incrível que é o TabNews, e inspirar pessoas a continuarem seguindo, mesmo com os problemas que encontramos no nosso código. Mesmo que ele esteja na nossa frente.

A thread foi escrita em 04 de agosto de 2021, portanto, quando me refiro a datas como mês passado significa que foi em julho de 2021, etc.

O Problema

Atualmente possuo um Bot para Discord que utiliza MongoDB como banco de dados. Sou iniciante na programação, então quando criei esse projeto não tinha o mínimo de noção sobre estruturação, ainda mais sobre banco de dados, então tudo o que eu fiz foi na base de tutoriais da internet. Estava indo tudo bem, até mês passado. A Menhera (o bot) cresceu de uma forma inesperada, e passou de 4000 servidores para 7000 em um piscar de olhos. A consequência de ter muitos servidores é, muitos eventos de mensagem sendo disparados para a aplicação, e tudo simultaneamente. Cada execução de comando fazia cerca de 3 queries para o Mongo:

  • Pegar os dados do Servidor (língua, canais permitidos, comandos desabilitados)
  • Pegar os dados do usuário (estado de comandos anteriores, economia, etc)
  • Pegar os dados do comando (se o comando estava em manutenção) Caso o comando fosse por exemplo, de economia, no decorrer da execução mais queries seriam executadas para alteras os dados do usuário. No início era tranquilo, mas derrepende o banco de dados começou a travar.

Situação Atual

Pra dar uma noção da situação do banco, o ping era de 315ms.

O cálculo de ping era feito com um insert na situação do bot para uma tabela, então um simples update demorava 300 milisegundos para executar.

Se já não bastasse isso, com mais de 1000 usuários usando comandos ao mesmo tempo, o banco de dados simplesmente parava de responder, travando todo o processo da aplicação. Naquela época, o bot estava executando cerca de 100 queries (query é uma requisição ao banco de dados) por segundo. Eu precisava de uma solução para isso, não era certo tantas queries assim por segundo. Foi então que um amigo meu, muito mais experiente, me recomendou Redis. Redis é um banco de dados em cache (salva os dados na memória volátil) com sistema chave-valor. Pesquisei afundo e era o que eu precisava, poderia remover duas queries já, a de comandos e a de servidor, já que estas raramente mudavam seu valor, e quanto mudam é pouca coisa.

A Solução (ou pelo menos o início dela)

Criei um container Docker (peguei um tutorial qualquer de como subir um container, já que nem fazia ideia de o que era Docker na época, só usei por que me recomendaram), integrei com o Bot e charaaam. Das 100 queries/s, agora eram executadas somente 60/s. Poxa uma bela redução, mas ainda não era o suficiente. Meu banco de dados não era dos melhores, não aguentava tanto pedido mesmo assim. O bot estava rodando normalmente, e as duas queries agora para o redis reduziu o tempo de respostas de comandos em torno de 70 milissegundos (170ms). Claro, para um Bot que nem estava rodando direito, isso já era ótimo, mas eu sabia que se o projeto continuasse crescendo, logo logo o problema retornaria.

Foi aí que eu encontrei o furo no meu código!

Depois de algumas horas relendo todo o código, linha por linha, eu encontrei uma coisinha que fazia sentido estar causando o problema. O Bot possui um módulo de AFK que identifica quando um usuário que estava anteriormente em AFK havia retornado, eu detectava isso ao usuário enviar uma mensagem. Para saber se o usuário estáva AFK, eu precisava ver o estado do usuário no banco de dados, antes de tudo, independente de qualquer outra lógica, independente se a mensagem era um comando ou um simples :) no chat. E ESSE ERA O PROBLEMA! Eu fazia uma query pro banco de dados antes de TUDO, literalmente TUDO. Então toda mensagem que o bot recebia, era uma query. Agora imagina, 7700 servidores spammando mensagem, onde a mensagem poderia até mesmo ser pessoas em algum canal random de Spamme aqui para ganhar esperiência. Eu fiquei frustradíssimo, pois era literalmente isso que havia ferrado tudo. Era um erro meu, puramente meu. Hoje em dia eu entendo que o que mais vai ter no código são problemas, até por que quem gera os problemas são os programadores (OwO). Estava literalmente na minha frente, acima de todo o código, lá pela linha 4 ou 5.

A Definitiva Solução

Feitoria! Problema encontrado, problema resolvido! Migrei o sistema de AFK para, ao invés de usar o MongoDB, usar o Redis. Então a aplicação fazia sim uma query pra cada mensagem que recebe, mas era uma query tão simples quanto um checkzinho à um objeto

    users[userId].isAfk

Código meramente ilustrativo

Agora o banco de dados só fazia requisições realmente necessárias para ler ou alterar valores na coleção users. Dessa forma, reduzi as requisições pro banco de dados para apenas 16 por segundo. Agora o bot está mais rápido, 66ms, e o MongoDB não está sobrecarregado! Eu demorei 1 semana e 5 dias para solucionar isso. O Bot ficou literalmente offline por 12 dias até que eu finalmente lesse as primeiras linhas do meu código e encontrasse o erro. De bônus, eu ainda converti todo a minha codebase de JS para TS quando fui migrar para Redis. Só ganhei com isso! Eu ainda quero utilizar Unix Socket para reduzir ainda mais o tempo de resposta de comandos, mas isso vai ficar pra outros tempos, eu não aguento mais banco de dados por agora.

Conclusão

Essa foi minha experiência com Redis que tive no passado. Caso queira ver a Thread original com imagens de exemplo, acesse a fonte deste artigo. Espero que isso sirva de exemplo para pessoas como eu, iniciante nesse mundo da programação. Confesso que no início me frustrei com esse problemão que eu enfrentei, mas é justamente isso que faz com que eu me apaixone por programação. Hoje, depois de um ano do ocorrido, eu percebi o rumo que quero seguir. Quero programar pensando na performance! Cada vez mais performance! Isso é o que eu me apaixonei. Obrigado a todos pela leitura. Essa é a minha primeira contribuição para essa comunidade maravilhosa do TabNews. Caso tu gostaste, e queira ver o código do projeto, pode ficar à vontade, é completamente Open Source

Para finalizar, gostaria de mostrar minha evolução desde o ano passado. Me apaixonei por performance, e até reescrevi um projeto JavaScript que usava para gerar imagens para essa minha aplicação, inteiramente em Go, reduziu o tempo de geração de imagens de 900ms para menos de 100ms em alguns casos.

Note que eu terminei o código Go faz nem 2 dias direito, e foi da mesma forma que eu fiz com todas as coisas novas que eu aprendo na programação, eu vou atrás de tutoriais, pego partes do que eu quero, e vou fazendo uma maçaroca de código de todos os lados, até ir aprendendo na marra e aperfeiçoando meu código. É assim que eu aprendo programação!

Muito obrigado novamente pela leitura. Beijos de Luz da Lux!

Que conteúdo legal, parabéns!!!

Que publicação sensacional Luan!!! Sou apaixonado por esse tipo de investigação e são nessas horas que nos tornamos programadores/engenheiros melhores! Muito obrigado por resgatar esse conteúdo do Twitter e trazer para cá 🤝

O TabNews possui várias margens para otimização e ler uma publicação assim só me deixa ainda mais motivado a caçar essas otimizações 👍