Conceitos fundamentais para se pensar escalabilidade
Olá!
Você já pensou como construir sistemas que tem como característica principal serem elásticos e aumentarem de tamanho conforme a demanda aumenta?
Nesse artigo, vamos passar por alguns conceitos fundamentais pensando em escala e visitar alguns exemplos de uso das técnicas mencionadas.
Vamos começar pela definição do termo:
Em telecomunicações, infraestrutura de tecnologia da informação e na engenharia de software, escalabilidade é uma característica desejável em todo o sistema, rede ou processo, que indica a capacidade de manipular uma porção crescente de trabalho de forma uniforme, ou estar preparado para crescer. (Wikipedia)
Parece bom, certo?
Afinal, todos nós queremos um sistema que lide com a carga de trabalho sem trabalho adicional por parte do time de desenvolvimento para adaptar o sistema para altas cargas de processamento, não importa se é um sistema web ou um sistema desktop de processamento de notas fiscais, em todos os tipos de sistemas, escalabilidade pode ser considerado uma coisa boa.
Existem duas formas de se fazer isso: seja aumentando os recursos de uma mesma máquina (escala vertical) ou adicionando nós (ou seja, mais computadores) a um cluster:
Para continuar a partir daqui, é necessário um entendimento sobre a diferença entre concorrência e paralelismo e alguns mecanismos relacionados, mais detalhes aqui.
Em linhas gerais, se eu vou rodar minha carga de trabalho de forma concorrente (usando reactor/event loops, por exemplo), faz sentido eu adicionar mais recursos para uma mesma máquina, afinal de contas, eles serão usados adequadamente.
A grande falha nessa abordagem é que ela tem um limite muito mais próximo de ser alcançado do que outras formas, afinal, máquinas disponíveis na Cloud chegam por volta de 3.5 Ghz por núcleo, sendo que o recorde mundial de clock está em 8.4 Ghz, mas não é comercialmente viável. Um outro problema é a memória. Por mais que você consiga memória exorbitante, como 256 GB de RAM ou similar, uma única máquina se tornaria um gargalo de um jeito ou de outro, por mais poderosa que ela seja.
Devido a isso e ao fato que a Lei de Moore está deixando de ser verdade (referências no artigo mencionado no começo dessa seção), urge a necessidade de irmos para outro caminho ao pensar sistemas: divisão e conquista.
Para utilizar a escala horizontal, é necessário construir o sistema de uma forma que cada um dos nós de processamento operem de forma independente dos outros. Para isso funcionar, os seguintes critérios tem de ser atendidos:
Unidades de trabalho atômicas: A menor unidade de trabalho tem que ser indivisível e independente de outros nós. Em termos mais práticos, imagine um carga de trabalho que utiliza todo o poder de processamento de um core (também chamado de CPU-Bound), que é processado sem dependência de banco de dados, rede, disco ou similares.
Ausência de estado compartilhado: Caso você tenha gestão de estado feita pelos nós, também não escala. Isso é pensado para utilizar o paralelismo em sua máxima capacidade. É só pensar: se cada nó tiver uma dependência de um outro componente para gerenciar o estado do processamento, esse outro componente terá que ser consultado muitas vezes (dado o paralelismo dos nós) e se tornará um gargalo, invariavelmente.
Aderência a Lei de Amdahl: Porque tudo nessa vida tem limite, até mesmo colocar processadores para resolver o problema.
Com as diferenças entre os tipos de escala, podemos deduzir que se eu escalar horizontalmente e tiver um processamento atômico bem definido, todos os meus problemas estarão resolvidos.
Na verdade… não.
Aqui é necessário uma outra técnica de roteamento chamada de load balancing. Afinal de contas, não adianta você rotear todo o tráfego somente para um dos nós, é necessário distribuir a carga entre os múltiplos nós de uma forma balanceada. Caso você tenha 10 nós computacionais disponíveis e só use somente um, você ainda estará sendo ineficiente. Em uma representação visual, temos uma estrutura parecida com essa:
Pronto, agora eu consigo usar todos os nós de uma forma adequada. Para quem quiser se aprofundar, existem vários algoritmos de roteamento, como Round Robin, URL Hash e muitos outros. Apesar de parecer um detalhe, pode influenciar na performance da solução.
Resolvido o básico de estrutura, vamos falar um pouco mais do código que está dentro dos nós deve se comportar. Dê uma olhada nessa imagem com calma e tenta colocar em perspectiva algumas operações computacionais:
Aqui é importante entender o conceito de ordens de magnitude. Uma consulta a memória (que tem a fama de ser muito rápida) é duzentas vezes mais lenta que consultas ao cache L1 dos processadores. Uma leitura de disco, é oitenta vezes mais lento que a memória. Em outros termos, falando de alta performance (e consequentemente, sistemas eficientes), a regra de ouro é: uso de CPU e memória em detrimento de disco e rede.
Agora, fazendo um comparativo com uma escala mais aderente a cognição humana:
Com isso dito, é daí que vem o sucesso de tecnologias como o Redis — que nada mais é do que usar a memória ao invés do disco como persistência da aplicação — para lidar melhor com altas cargas de processamento. Para quem não conhece, essa técnica se chama Cache. Ou GraphQL, que, ao agrupar várias chamadas distintas de rede em um mecanismo de cache, evita diversas consultas adicionais que geram um impacto considerável na performance — e consequentemente, na escala — das aplicações.
Espero que o texto tenha sido útil e esclarecedor para você!
Até!
Você gostou do conteúdo e gostaria de fazer mentoria comigo? Clique aqui e descubra como.