O desafio de escalar aplicações sem fazer over-engineering
Como nos preparamos para a obsolescência programada do nosso ecossistema
Onde Vivem os Monstros
Pra mim, é muito difícil falar desse assunto sem antes dar um disclaimer. Eu comecei a minha carreira como engenheiro de software em 2003, Java 1.4 e tirei certificação em 2006 para Java 5. Esse momento da certificação foi muito importante. Durante o processo de certificação eu estudei algumas melhores práticas que eram MUITO interessantes. Agora, tinham alguns pontos da linguagem que era "hum… bom saber que existe mas muito provavelmente, eu nunca vou usar isso". Claro, para um contexto de certificação, faz sentido você saber cada detalhe da linguagem. Mas para a vida real, era algo muito pouco aplicável. Esse sentimento só aumentou conforme fui trabalhando em outras empresas. O que quero dizer é: em linhas gerais, tirando casos muito específicos, não fazemos software para Nasa. Isso não é um grito pela mediocridade e sim um apelo a soluções que sejam do tamanho certo. Nem mais, nem menos.
Depois em 2009, quando fui me certificar em Scrum, comecei a ler materiais sobre a frequência de utilização de funcionalidades em uma aplicação. Um gráfico mostrava que grande parte das features nas aplicações eram usadas pouquíssimas vezes. E daí a ligação com usar a abordagem incremental para se construir software. Isso veio ao encontro com aquilo que eu achava: no final o over-engineering é muito mais uma questão de ego (para mostrar o quanto mais sofisticado aquela solução pode ser, ou o quão antenado em trazer um componente novo) ou algo similar do que algo prático. Na vida real, muitas vezes, aqueles 5 dias a mais que se leva para fazer uma feature usando o componente ou pattern da moda, não se paga. Bem como, nesse processo de estudar projetos, estudei para certificação PMP e conheci outro conceito: Gold Plating. Que casa com over-engineering.
Em linha gerais, o que tenho visto nesses quase 20 anos na indústria de software é que é muito melhor construir algo pequeno e ir adaptando conforme a necessidade e comprovação do uso e teste da hipótese, do que perder tempo pensando em N possibilidades, e N cenários que talvez, nunca vão ocorrer. Se ocorrerem, você pode pensar em dar uma saída elegante, ainda que simples. De 2003 para cá, muita coisa mudou em software (ainda bem). Servidores de aplicações gigantes e monolitos, se fragmentaram na abordagem de microsserviços. On premise para on cloud. Serverless, filas. Ou seja, conceitualmente, muita coisa mudou e muito código foi para o lixo. Aqui é importante separar A de B para não soar estranho: muito código vai para o lixo. Temos que nos acostumar com isso e não ficar tão apegados em fazer “a melhor solução” de todas para aquele cenário. E sim ir melhorando conforme o uso (e necessidade), constantemente, apoiando em novos componentes técnicos ( com fundamentação teórica, pois só a praxis não vai resolver). Ou seja, muito código VAI para o lixo. E isso NÃO significa que o código TEM QUE SER um lixo.
A falta de afinidade pelo over-engineering e pelo show-off vibe talvez tenham me ajudado a sair da programação. Eu ainda amo programar. Não que eu faça com frequência, ao contrário. Mas a sensação de resolver um problema no código e chegar ao final do dia com uma peça de software feita, lembrando que no começo do dia você não tinha a menor noção de como começar aquele problema, é fantástica. Por outro lado, me lembro que na época em que eu estava programando, a ideia de fazer soluções O(n log n), ou usar isso ou aquilo para o código ficar “melhor” (super subjetivo) foi me tirando a vontade de pertencer àquele meio. Pelo exato motivo do começo do texto: eu precisava ler um arquivo, dar um POST em uma URL, transformar um dado, etc. Algo relativamente simples. Que não demandava aquela engenharia toda. Para finalizar, friso mais uma vez, não é que o conceito não seja importante (ele é em contextos específicos como segurança, financeiro, etc), ele só não é mais importante do que software funcionando. Vou usar esse exemplo do o(n log n): eu nunca entendi plenamente isso. Sempre achei que em algum momento na minha vida de programador isso iria me assombrar. Esse momento até agora, nunca aconteceu.
Como escalar algo que nunca para de crescer?
No nosso planejamento para a Black Friday 2020 na Mandaê vimos que havia um componente específico que não estava reagindo bem à carga. Questionei um dos nossos bruxos que além de conhecimento técnico carregam um bruto histórico da Mandaê, e ele foi categórico: "Isso foi construído numa época que nosso volume diário era de 200 encomendas por dia". Nosso volume diário médio hoje é acima de 100 vezes o que aquele componente foi projetado. E eu chateado porque ele não aguentou a carga. A Mandaê cresce de 70 a 100% todo ano. Provavelmente para essa Black Friday de 2021 teremos que rever algo que foi feito há não tanto tempo atrás, como 2018. Quando pensamos em um ecossistema de microserviços, temos filas, caches, api gateways, para tentar não apelar para escalar horizontal ou vertical um serviço. Mas ainda assim, tem um limite. Focando no lado bom, é um excelente momento para reprojetar o serviço de 2018 com as tecnologias atuais, e com mais 3 anos (2019,20,21) de experiência de Mandaê para calibrar a demanda. Será que vale a pena pensar em uma solução que vai aguentar mais 2x? 100x? Será que quando chegar em 30x, já não teremos outra maneira de resolver o problema? Esse é o tipo de dilema que passamos todos os dias.
Darwin aplicado a arquitetura de software
Se lembram do “não é o mais forte que sobrevive, nem o mais inteligente, mas o que melhor se adapta às mudanças". Quando aplicamos isso para software, também faz sentido. A melhor arquitetura é a que se adapta à necessidade da empresa. Principalmente a necessidade de velocidade. Por isso, pensar em como absorver pequenas mudanças na arquitetura (escalabilidade, concorrência, segurança) é mais importante do que ter a melhor em uma só vertical e ser ruim nas outras. Como o livro Building Evolutionary Architectures diz: todo software evolui com o tempo, planeje para mudança.
A vida imita a arte mas…
Na minha experiência o software imita a empresa em que ele foi feito. Seja a organização, organização política e camadas e mais camadas de complexidade. É por isso que, para mim, é muito importante patrocinar software simples. Simples de fazer, simples de dar manutenção e simples de mudar e refazer também. Se tivermos que gastar tempo pensando, que não seja em complexidade e sim em ser simples. Normalmente as coisas evoluem de simplório para complexo e de complexo para simples. Simples é elegante. E não é simplório. Pensar simples talvez seja mais difícil que pensar complexo. Esse patrocínio é importante porque os engenheiros de software vão buscar respaldo das suas ideias em quem eles têm contato. Se eles virem complexidade, e um monte de camada política e burocrática, não exija que eles façam mágica.
Obs: O conceito de navalha de Occam (entre o complexo e o simples com resultados similares, opte pelo simples) pode ajudar também nesse fundamento.
O que temos feito, então?
Temos trabalhado muito com a evolução da nossa própria arquitetura. Estamos operando desde 2014 on cloud. Significa que também já passamos por várias versões dos componentes da AWS, por exemplo. Mas também fizemos software para cenários que faziam sentido na época e hoje resolvemos o problema de outra forma, ou o problema mudou. Em escalabilidade, o scale up dos EC2s na AWS nos ajuda quando necessário, bem como o EKS com os triggers para criar mais pods quando necessário. Hoje avaliamos mudar alguns serviços que usam fila de SQS para Kafka. E aqui vem um dos maiores aprendizados nosso com esse cenário. Um bom engenheiro de software não é o que sabe usar tudo, ou sabe usar alguns itens com uma profundidade enorme. Para nós, bom mesmo é quando ele sabe quando usar A ou B. E não só A ao invés de B. O B tem suas vantagens em alguns cenários. Procuramos justamente engenheiros que saibam diferenciar e fazer esses trade-offs nas tecnologias. Sejam elas jpa/jdbc, sejam fila comum (como um SQS) ou um Kafka, um double de um big decimal e por aí vai. O problema de ser passional com um só tipo de tecnologia é que a chance é muito grande de só conhecer uma ferramenta. E como se brinca: para quem só tem martelo, tudo vira prego. Se você só conhecer Kafka, vai querer usá-lo quando uma fila simples faria o trabalho. Se você é apaixonado por serverless e quer implantá-lo a todo custo, pode ter que fazer uma volta enorme para ter que usar. Só por usar. Pode trazer um ou dois benefícios, mas traz 10 dores de cabeça desnecessárias quando se usa essa abordagem.
Por fim, o patrocínio desse tipo de pensamento dentro da equipe de engenharia se paga quando temos uma cultura no time que se auto-regula nesse sentido. Quando alguém faz algo exagerado ou sem justificativa, o peer review, pega e sugere alteração. Esse comportamento se auto-regula, e isso é cultura.
Aos poucos o mindset do time vai ficando formatado em buscar soluções e adaptações para as mudanças e não profundidade em uma única solução. Como citei Darwin para falar de adaptação, aproveito para fechar o texto com uma reflexão também dele: "É necessário olhar para a frente da colheita, não importa o quão distante isso seja, quando uma fruta for colhida, algo bom aconteceu."