E a gente começou a ter outro problema clássico de crescimento que é legal. Toda essa arquitetura começou a se degradar. Depois do pico, recorde, dali para a frente foi só ladeira abaixo, em relação a erros acontecendo, problemas aqui e ali, reduzindo a capacidade de processamento e a gente correndo atrás de tudo isso, e chegando em situações de colocar em xeque a nossa estabilidade. E um problema sempre em questão até pareto, ou de ironia que seja… todo problema afetava esse grupo específico de clientes, que têm muito dinheiro, têm muito apetite, estão aqui, têm um acordo comercial e a gente não está conseguindo cumprir o acordo comercial com eles, porque toda hora alguma coisa soluça acaba pegando esses caras que estão 24/7. Então, a chance deles estarem expostos a um problema é muito maior do que quaisquer outros clientes.
E a gente chegou na máxima, olhando e dizendo: “é legal, a gente cresceu, fez várias coisas, contratou mais gente. Temos ótimos sistemas locais, mas quando juntamos todas as peças começa a ver que a coisa não fica tão boa assim.” O exemplo clássico do que a gente tem, do que acontecia aqui dentro da nossa plataforma de trading… A gente veio tirando coisas no monolito já há alguns anos, mas mesmo a estratégia de microsserviços seguiu meio que a mesma máxima do que do que tinha de refatorações dentro do monolito que eram boas iniciativas, boas ideias só que não eram concluída, porque muda a dinâmica da empresa, muda alguma outra prioridade, e a gente não conseguia fechar algumas coisas, então, chegamos na máxima que muita gente na empresa me ouviu repetir, por muito tempo, que padrões são legais, vamos ter vários.
Essa ironia é assim, porque chega uma hora que fica bem difícil. Nessa situação, tínhamos o monolito que é Core, com o DB dele também gigantesco, e que é Core. Coisas, como relatórios específicos, vem desse banco de dados. Tem uma dependência forte. Quando começava uma transação, ele criava aqui alguma coisa nessa base de dados, chamava o serviço A via http, um protocolo bacana que todo mundo conhece bem, o serviço A roda direitinho. O serviço A faz coisas na sua base local, publica no evento e vai ter um consumidor que vai retroalimentar aquela base core também com o que precisar.
Depois disso, chamava o serviço B usando o gRPC aqui, a questão também dos padrões e dos protocolos misturados. E também faz coisas em sua base local, basicamente manda eventos, e tem o consumo depois que vai lá alimentar a base core.
O detalhe é que a transação que o cliente… a experiência do sincronizada da API, só terminava depois daquele 3.1. Depois que o serviço B terminava o trabalho dele, era que a gente devolvia para o cliente a ação. E na época a gente não conseguiu conectar esse cara python aqui tava usando NATS, que é um sistema de mensageria totalmente por GO. É um projeto muito legal mas complicado, porque não tem toda a comunidade que tem os projetos, então, a gente por vezes ficou no escuro em relação ao que fazer com esse cara, como otimizar como fazer algumas ações. E aqui a gente tinha a questão legal: como a gente vai esperar, então, literalmente o estado do registro do banco de dados mudar, faz um pooling com o Macron aqui.. desculpe, faz um sleep e pooling.
Então, o que ele fazia era: fiz, coloquei no banco, chamei http, chamei em gRPC, começa um processo de: olho no banco. Mudou o status? não. Sleep. Olha no banco. Mudou o status? Não. Sleep. E esse sleep achamos que estávamos fazendo uma coisa legal no começo para não forçar tanto esse banco vamos fazer ele aumentar gradualmente tem até padrões de projetos para isso para você bater mais devagar nesse banco. Então, tudo em cada momento eram boas ideias. Melhor que a gente podia fazer a cada momento.
Os problemas disso eram que, entre o monolito e o serviço A, a gente fazia um literalmente start transaction, um insert chama o serviço A, se der bom, um commit, se dar erro, um rollback. Simples assim. Então, tinha aqui algumas regras de negócio que a gente atendia. O serviço A fazia validações, devolvia um erro 400 qualquer coisa, ou qualquer erro mapeado. Legal, é para fazer o rollback mesmo. Os problemas disso são que o serviço A deu time out. Por algum motivo eu estava aqui ocupado e ele tomou um time out. O monolito é erro, rollback. Só que o time out não era propagado aqui, não tinha bastante contexto, o cancelar e passar a diante. O serviço A terminava o que ele estava fazendo. A gente começou a ter problema de sincronização entre o que estava na base A e na base c Core.
Outra situação também na mesma linha, passou o redondinho tudo no serviço a vai chamar o serviço B de gRPC, a mesma coisa aqui. Eventualmente nós tínhamos problemas de perda daqui e aqui ainda ficava uma outra situação interessante, porque se o serviço B não processar é um caso mais curioso, porque ele dava um erro na camada do gRPC que nós não sabemos se o serviço B tinha processado ou não, e boa parte das vezes ele não processava. E voltar para o cliente no http dependia do serviço B processar e atualizar o status depois que ele processava. Então, aqui era um caso que o cliente também ficava no escuro quando acontecia.
E assim a cereja do bolo desse desenho, que gerou algumas war rooms no final de semana, era justamente o temporizador aqui, porque o sleep não consome CPU, não consome memória é um cara… é o blocker ali. E enquanto eu fiz a chamada ou não fiz alguma coisa, estou esperando a resposta disso. Eu tinha um processo de API ocupado fazendo nada. Aqui, bloqueado.
Com isso, com aqueles clientes grandes que chegavam batendo forte, qualquer hora que chegava… a gente chegou a ter situações de está com tanta thread ocupada na API do sistema, para fora cair e a gente começava a dar erro 500 simplesmente porque não tinha mais Pod suficiente para responder, inclusive porque a regra de autoscaling falhou, porque o autoscaling está baseado lá na CPU, que não mudou uma vírgula nisso daqui.
Então, é muito legal porque é aquela história: “poxa, mas a gente ensaio tudo numa cartilha tão boa, eu tenho autoscaling, tenho várias coisas e continua dando problemas, por quê?” Então, isso tudo foi mega interessante e o ponto principal disso, acho que tem diversos casos legais, mas a gente tinha muita dificuldade de identificar qual era o estado certo da coisa. Porque a coisa foi para o sistema A, mas ele devia estar em qual situação, cruzar as bases. A gente estava com as coisas bem caóticas e o pior de tudo esse cliente lá 24/7, extremamente crítico, que a área comercial queria fechar com SLA baixíssimo, a gente não tinha como se comprometer com nada, porque para a gente achar as coisas era muito difícil. Muito caótico.
E veio a parte do diálogo de muitos milhões de dólares com CTO. O CTO entrou na empresa no começo do ano passado, depois que o SoftBank fechou o aporte. Um cara de vasta experiência na IBM, Cisco, um monte de lugares do Vale do Silício. E ele ficou um tempo observando e aprendendo o que estava acontecendo e ele veio aqui e disse: “oh, Rafa, a gente está com problema de gestão global de estado das coisas e, assim, a gente pode usar Saga porque saiba funciona para resolver isso.”
Vamos pegar livros, palestras, tudo que a gente achar de conteúdo e todos os exemplos. Você acha muitos exemplos do carrinho de compras e eu fiquei muito irritado com isso. O título vem daí porque não é possível esse “troço” é só para e-commerce. No nosso caso a gente não é e-commerce. A gente tem um troço mega complexo que o cliente espera que seja síncrono e rápido ainda. Com uma baixíssima latência. Não estou dizendo que não funcione, eu vi lá. Vi todos os diagramas e é lindo aquilo na teoria mas o carrinho de compra foi construído no o conceito assíncrono. Eu vou lá, depois eu espero o boleto pago, pois é assíncrono. Espero o cartão de identificação, é assíncrono. O meio de pagamento em si é assíncrono. Por mais que a gente tenha uma corrida para fazer esse processo cada vez mais rápido, ele é assíncrono por definição e a gente não. Como a gente vai contornar isso, trabalhar? E ele veio com a resposta: “eu estou falando de um padrão que funciona. Como o mercado olha isso…me diz se ele serve ou não. E se não servir me fala o que serve então, mas temos que resolver mas temos que resolver.”