Como as coisas escalam: uma introdução ao design de software escalável do mundo real

Dec 03 2022
A intenção por trás deste post é expor alguns fundamentos do design de sistemas de software — com ênfase extra no aspecto do design. Ele responde a uma pergunta, por meio de um exemplo de caso real, que muitos engenheiros se perguntam: como começo a projetar um sistema? Descobri que muitos recursos on-line se concentram nas ferramentas e na abordagem de “sujar as mãos”.

A intenção por trás deste post é expor alguns fundamentos do design de sistemas de software — com ênfase extra no aspecto do design .

Ele responde a uma pergunta, por meio de um exemplo de caso real, que muitos engenheiros se perguntam: como começo a projetar um sistema?

Descobri que muitos recursos on-line se concentram nas ferramentas e na abordagem de “sujar as mãos”.

Embora dominar as ferramentas seja importante, estamos, com mais frequência do que gostamos de admitir, ignorando a peça central do quebra-cabeça; o pensamento de alto nível de como as coisas funcionam antes mesmo de tentarmos codificá-las em nossas chamadas linguagens de programação.

No fundo, “a arte da programação é a arte de organizar a complexidade” (Farley, David. Modern Software Engineering).

A complexidade da organização deve vir antes de você começar a digitar qualquer código (ou talvez até mesmo pensar em sistemas de computador!).

Assim, vamos mergulhar em um exemplo de caso real de como podemos utilizar conceitos que vão garantir que uma determinada instância do nosso sistema consiga escalar e identificar quais são esses conceitos.

Preparar-se!

Deixe-me dar um pouco de contexto sobre o Creator Now .

No centro do nosso negócio, precisamos gamificar as informações sobre os canais dos criadores no YouTube para oferecer a eles o melhor aprendizado e experiências crescentes para a jornada do criador.

Isso significa que, para cada novo usuário cadastrado, precisamos contar muito com a API do YouTube para extrair informações sobre a vida de seu criador.

Queremos garantir que essas informações sejam atualizadas com a maior frequência possível. Afinal, não faz sentido manter um produto em que a principal informação que orienta a experiência do usuário está atrasada em relação à realidade do “mundo real”.

Este é o nosso principal desafio:

Como podemos escalar se tivermos que contar com uma API de terceiros com limites que estão fora de nosso controle?

Primeiro, vamos entender como a API do YouTube funciona e onde está realmente o problema usando uma analogia do mundo real.

Imagine que a API do YouTube é um grande escritório que mantém os dados de todos os canais — e tudo relacionado ao YouTube.

Toda vez que alguém na internet quer consultar informações sobre um determinado canal, ele vai até lá. Em suma, todos eles precisam enviar alguém a este prédio para solicitar essa informação.

O fluxo é bastante simples: alguém entra pela porta da frente. Eles apresentam suas credenciais ao porteiro. Uma vez autorizados a entrar, eles são atendidos por um feliz funcionário do YouTube:

  • Olá senhor; qual é a informação que você deseja?
  • Você pode verificar as informações mais recentes no canal CatsAreAmazing ?
  • Claro, deixe-me verificar nossos arquivos! <pega os dados do canal e devolve um relatório de uma página dele>
  • Obrigado, bom senhor!
  • Espero que os escritórios do YouTube sejam mais novos do que isso!

Mas o YouTube experimentou uma quantidade enorme de pessoas pedindo coisas.

Como todo mundo está tentando obter muita informação o tempo todo, eles decidiram colocar algumas regras em vigor ( isso não reflete as regras do mundo real da API do YouTube ):

  1. Você só pode solicitar informações de no máximo 50 canais/vídeos a cada visita ao escritório
  2. Você só pode entrar no escritório 10 vezes por minuto, 10.000 vezes por dia
“Nãããão! Eu realmente queria obter todas as informações sobre todos os canais do mundo!”

Ok, então temos que ser espertos sobre isso.

Vamos fazer algumas contas rápidas para descobrir o número máximo de informações de canal que podemos solicitar por minuto (e por dia).

Se pudermos ir ao escritório 10 vezes/minuto e a cada vez, podemos solicitar 50 canais: 10 x 50 = 50 canais/min. Fazendo uma matemática semelhante, percebemos que podemos solicitar 10.000 x 50 = 50.000 canais/dia.

Se você expandir um pouco mais, perceberá que podemos usar toda a nossa cota em 16 horas e 40 minutos — ou seja, esse é o tempo que levará se solicitarmos 50 canais/min para explodir nossa cota de 50 mil canais/dia.

Então, a pergunta de ouro aqui é:

Como podemos manter nosso aplicativo atualizado de maneira mais eficiente com as informações mais recentes dos usuários (mesmo que tenhamos mais de 50 mil usuários)?

Primeiro, vamos estabelecer algumas regras básicas para nós mesmos que nos farão economizar algumas idas ao escritório do YouTube e garantir o cumprimento de suas regras. Vamos dividir as responsabilidades desta grande tarefa em tarefas menores que são executadas por diferentes departamentos da nossa empresa:

  1. Vamos ligar para o nosso departamento responsável por entrar em contato com o escritório do YouTube, o Scraper .
  2. Não precisamos solicitar informações de novos canais se já obtivemos informações sobre eles nas últimas 24h (aceitamos um atraso de 24h para atualizar os canais).
  3. Isso significa que precisamos lembrar as últimas informações sobre um canal de alguma forma e a hora em que o recuperamos — vamos chamá-lo de nosso Gabinete .
  4. Provavelmente queremos que mais de uma pessoa possa ir ao escritório — como o YouTube tem um limite de 50/min, podemos otimizar nosso processo contratando 50 pessoas para executar esse trabalho (vamos chamá-los de Runners )
  5. Precisamos ter algum tipo de controle sobre quantas visitas fazemos ao escritório deles em um determinado minuto e em um determinado dia (talvez possamos anotar em algum lugar? Vamos chamá-lo de Registro de Corredores ) .
  6. Isso também significa que, cada vez que nossos corredores quiserem sair e obter novas informações sobre o canal, precisamos verificar novamente se eles não correm o risco de perder a viagem devido à extrapolação de nossa cota diária/minuto. Este centro de controle centralizado será chamado de Departamento de Corredores .
O rascunho inicial do nosso fluxo de trabalho

Agora, o que há de tão especial nesse fluxo além do fato de que ele nos dá uma solução inicial para (a maioria) dos nossos problemas?

Ele fornece separação clara de preocupações :

  • O Scraper só se preocupa em obter informações para um canal do YouTube (não importa como)
  • O gabinete só se preocupa em armazenar/recuperar informações de um canal
  • O Departamento de Corredores se preocupa apenas em lidar com as corridas para o escritório do YouTube
  • O Runners' Log se preocupa apenas em armazenar/recuperar informações das corridas
  • O Corredor Atribuído só se preocupa em fazer a corrida em si

Este também é o chamado conceito de Modularização amarrado com Loose Coupling .

Vamos expandir um pouco o Loose Coupling e como isso torna nosso sistema muito mais escalável. Para entendê-lo, precisamos entender por que as interfaces (contratos) entre os módulos são importantes.

Vamos pegar a interação entre o Scraper e o Cabinet , por exemplo.

Ambos precisam concordar sobre como vão se falar: o Gabinete vai receber as solicitações por telefone? Um SMS? — Eles também precisam concordar sobre como o Gabinete vai devolver as informações ao Raspador: é uma pasta com arquivos? Um anexo via e-mail?

O que importa é que eles precisam ter um acordo, uma interface, um contrato.

Por quê?

Uma vez estabelecida essa linha, não importa quão complexa seja a estrutura interna do Gabinete : para todos os cuidados do Scraper , pode ser uma operação de uma pessoa ou um exército de construção de cinco lojas.

Contanto que o Gabinete possa cumprir seu contrato de maneira confiável, está tudo bem.

Isso permite que nós e nossa equipe de desenvolvimento trabalhemos de forma independente em cada um desses módulos, tomando as melhores decisões para seu desempenho interno.

Podemos paralelizar o trabalho de cada um deles, porque no final o que importa é que eles saibam se comunicar.

Esse conceito é aplicado até mesmo no nosso dia a dia: sempre que você faz um pedido online, você costuma se importar com a transportadora? Você sabe como funciona internamente uma calculadora para entregar os resultados? Você ao menos se importa?

Que beleza, hein?

E se tivermos mais solicitações do que podemos atender?

Droga, isso não resolve um dos nossos principais problemas, ainda! Vamos criar um acúmulo que o Departamento de Corredores pode não conseguir resolver a tempo!

Ei, oi. Isso não parece divertido.

Parece que precisamos ter algum tipo de sistema para garantir que tenhamos um acúmulo dessas operações e tratá-las da maneira mais eficiente possível, sem deixar solicitações pendentes indefinidamente!

Vamos criar algumas regras para dimensionar o número de solicitações ao Departamento de Corredores .

Algumas coisas ( regras ) que podemos criar são:

  1. Primeiro a chegar, primeiro a ser atendido: tratamos os pedidos como uma fila. Isso garante que, em algum momento, a solicitação de obtenção de informações para um determinado canal será atendida. Só não podemos garantir quando.
  2. Priorizar canais para os quais não temos dados (novos canais em nosso app). Essa pode ser uma estratégia inteligente: um canal sobre o qual não temos dados significa que o usuário provavelmente não conseguirá usar o aplicativo. ou seja, ter dados desatualizados é melhor do que não ter nenhum dado!
  3. Certifique-se de que não temos solicitações duplicadas em nosso backlog: se já houver uma tarefa pendente para um canal específico, podemos simplesmente descartar outras solicitações para ele.

Vamos dar um exemplo para ver se tudo isso faz sentido.

Começamos nosso dia do zero - sem pedidos pendentes no backlog.

De repente, temos um fluxo de 7.000 solicitações: o aplicativo está desesperado por informações sobre 7k canais! Dada a lógica que descrevemos acima, nós:

  1. Limpe todas as solicitações de canal duplicadas que possam estar neste lote
  2. Verifique para quais canais não temos informações anteriores; mova esses canais para a frente da fila
  3. Armazene a fila em nosso Runners' Log

Mais uma vez, aqui está a beleza da modularização novamente: esta é uma mudança interna na forma como o Departamento de Corredores opera.

O Scraper ainda funcionará da mesma maneira: continuará pedindo informações sobre os canais (como uma criança mimada que é!). É problema do Departamento de Corredores armazenar e tratar esta fila internamente.

Lembre-se: todos os outros módulos se preocupam com contratos e interfaces!

Ufa!

Tudo bem, amigo, isso foi muito - mas espero que tenha feito tanto sentido para você quanto para mim ao escrever isso.

Agora você deve ter um bom entendimento de por que modularização, baixo acoplamento e abstrações são importantes.

Agora você também sabe como podemos, antes mesmo de começar a digitar o código, pensar em projetar um sistema que possa ser dimensionado de forma independente.

Da próxima vez que você começar a projetar um sistema, tente responder às questões fundamentais de como você pode projetar esses módulos independentes e frouxamente acoplados antes mesmo de começar a digitar o código.

Caso contrário, você pode acabar desenvolvendo o código legado de amanhã que ninguém entende e/ou quer lidar!