Design de aplicações escaláveis sem magia
Aproveitar o poder de cálculo do cliente para alto desempenho com muitos utilizadores
- Publicado
- Atualizado
- (criado )
- Escrito por
- Willem L. Middelkoop
- Temas
- nuvem · dados · design · tutorial · iOS · lemmid-store · programação · servidor · smartphone · web · webdesign
- Versão humana deste artigo
- https://willem.com/pt/2020-05-11_design-de-aplicacoes-escalaveis-sem-magia/
- English
- Scalable application design without magic
- Nederlands
- Schaalbaar applicatieontwerp zonder magie
- Deutsch
- Skalierbares Anwendungsdesign ohne Magie
- Español
- Diseño de aplicaciones escalables sin magia
- Français
- Concevoir une application scalable sans magie
- Italiano
- Progettare applicazioni scalabili senza magia
- Papiamentu
- Desèño di aplikashon eskalabel sin magia
- Frysk
- Skaalbere applikaasje-ûntwerp sûnder magy
- Idiomas
- Traduzido do original em inglês por um modelo de linguagem em duas passagens (tradução e revisão) com um glossário e um corretor ortográfico; ainda não revisto por um falante nativo.
Resumo
No âmbito da app de pedidos de comida online que estou a construir, precisei de desenhar uma infraestrutura backend escalável, capaz de lidar com muitos utilizadores em simultâneo. A escalabilidade é vista como um problema difícil de resolver. Muitas vezes é apresentada como algo mágico, feito por empresas de milhões de dólares com ferramentas secretas. Mas magia não existe, ou existe?
Texto completo
O que é a escalabilidade?
Se estás a construir uma app, provavelmente começas bem pequeno. Só alguns utilizadores (ou só tu, o programador) a trabalhar com a app. Está tudo ótimo: a app funciona bem, é rápida e não há problemas. (aprecia este sentimento!)

Neste cenário inicial e pequeno, o único servidor que corre a app consegue aguentar o utilizador que interage com ela. De cada vez que o utilizador toca ou clica num botão, o servidor único faz algum trabalho. Se programares bem, estas cargas de trabalho podem ser otimizadas em pequenos blocos eficientes. Fazê-lo assim é um exemplo típico de uma arquitetura de software monolítica.
Mesmo que o teu projeto ou app seja bastante pequeno, a certa altura pode chegar uma afluência de utilizadores. Se o teu sistema não aguenta cargas elevadas, tem grandes probabilidades de falhar.

Por causa do design monolítico da app, todos os utilizadores interagem com o mesmo servidor. Toda a sua interação (toques, cliques, entradas) é tratada por esse servidor. Com uma carga maior, ele tem de trabalhar muito mais. A certa altura, vais reparar que as coisas abrandam, porque o servidor não consegue acompanhar a quantidade de trabalho que tem de fazer. Isto significa que a tua app não escala bem, ou seja: estás em apuros.
Falhar no "momento supremo"
Não escalar bem é um grande problema que ninguém deve subestimar. A maioria das apps (e dos seus modelos de negócio) depende de grandes volumes com pouca receita por utilizador. Se queres que a tua app seja um sucesso (financeiro), tens de garantir que funciona bem quando há um aumento súbito de tráfego. Se a tua app falha quando de repente recebe a atenção das massas, vais perder uma oportunidade "única na vida" de crescimento (orgânico).
De certa forma, desenhar para a escalabilidade é como manteres o cinto de segurança apertado quando estás sentado no teu foguetão construído por ti, com motores experimentais. Nunca sabes bem quando vai acender, mas quando acender é melhor que estejas bem preso (e aproveita a viagem!).
Formas comuns de alcançar a escalabilidade
Já se escreveu muito sobre como construir apps escaláveis, e as formas comuns de melhorar a escalabilidade são:
- Aumentar a capacidade do servidor: mais memória, mais CPU, mais armazenamento. Funciona, mas só te leva "até certo ponto".
- Adicionar mais servidores: em vez de um único servidor a fazer de gargalo, podes juntar mais servidores que partilhem a carga de trabalho. É mais fácil de dizer do que de fazer, porque exige balanceamento de carga e partilha de dados, o que pode ser complicado (por exemplo, se tens um contador de pedidos e dois servidores a contar, qual deles determina o número seguinte?)
- Otimizar o código: usa as ferramentas certas! Escolhe uma linguagem de programação orientada para o desempenho e software de servidor à altura. Estuda a programação funcional para o teu código poder correr em vários núcleos de forma assíncrona. Torna-o sem estado. Faz benchmarks. Otimiza. Cada milissegundo ganho num único pedido soma-se depressa quando lidas com grandes volumes.
- Usar a base de dados e separá-la do servidor da aplicação: se usas uma base de dados, aproveita o seu poder! Tira partido da cache de consultas, dos índices e das capacidades de pesquisa. A maioria das soluções de bases de dados oferece opções de clustering à prova de bala; não tentes reinventar a roda que outros já aperfeiçoaram.
O melhor que podes fazer é ponderar todas estas opções. Mesmo que não vás juntar mais servidores já, deves escrever o teu código para o suportar mais tarde. Fazer benchmarks e otimizar o código deve ser parte integrante do teu trabalho, e não uma ideia de última hora.
Aproveitar o poder de cálculo do cliente
Depois de ponderares todas as formas comuns de alcançar a escalabilidade, há "mais uma coisa".

Poucos programadores pensam nisto: o problema da escalabilidade contém a solução! Quando tens 1000 utilizadores, tens 1000 computadores com CPU potentes e muita memória. Os computadores e smartphones modernos tornaram-se cada vez mais capazes.
Só tens de encontrar uma forma de tirar partido do poder de cálculo do cliente. Pode parecer uma maldade (usar o poder de cálculo do utilizador), mas vai dar-lhe uma experiência muito melhor e mais rápida. O melhor é que o poder de cálculo deles é gasto na experiência fantástica deles, e resolve o teu problema de escalabilidade como feliz efeito secundário.

Ao aproveitares o poder de cálculo do utilizador, reduzes a quantidade de trabalho que o servidor tem de fazer. Em vez de o servidor fazer um pouco de trabalho de cada vez que o utilizador toca ou clica num botão, este trabalho passa a ser feito sobretudo pelo dispositivo do utilizador. Isto é mais eficiente do que comunicar com um servidor por Wi-Fi/4G (poupa bateria e tempo).
Exige repensar um pouco, mas este princípio também se pode aplicar a aplicações web. Em vez de tratares de tudo no servidor, podes fazer muito trabalho no cliente. Coisas como pesquisar, navegar, filtrar, mover-se pela página e até adicionar artigos ao carrinho podem ser feitas com JavaScript do lado do cliente. Tu, caro programador, tens de procurar formas de o fazer funcionar, mas se conseguires terás escalabilidade quase infinita ao teu alcance.
Na prática: a app de pedidos de comida
Para a app de pedidos de comida procurei formas de aproveitar o poder de cálculo do cliente para alcançar alto desempenho. O principal desafio dos pedidos de entrega e para levar é que têm picos à hora do jantar. Muita gente usa a app mais ou menos à mesma hora, e isso é uma receita para problemas de escalabilidade.

Se pensares em pedir comida online, podes dividir todo o processo em blocos mais pequenos:
- 1) Abrir a página: carregar a app/página
- 2) Explorar as diferentes opções: ver a lista de produtos, as descrições, os preços, as fotografias, etc.
- 3) Procurar algo específico: pesquisar por categoria, nome do produto, etc.
- 4) Escolher um produto: juntá-lo ao teu pedido
- 5) Personalizar um produto: escolher um molho, um acompanhamento, uma cobertura, etc.
- 6) Introduzir os teus dados: o teu nome, número de telefone, dados de entrega, etc.
- 7) Pagar o pedido: escolher um método de pagamento, ligar ao banco ou ao cartão de crédito
- 8) Fechar a app
A maior parte destes passos pode ser programada de forma a que o servidor não seja necessário. Só o passo 1 (carregar) e o passo 7 (pagar) exigem contacto com a infraestrutura backend. É importante perceber que os passos 2 a 5 se repetem várias vezes, pois é normal os utilizadores juntarem vários produtos ao pedido.

Separando a carga de trabalho restante por tarefa, o software pode ser ainda mais otimizado. O "servidor de produtos" está otimizado para servir recursos estáticos (textos, preços, imagens) que permitem ao cliente criar a experiência de navegação pelos produtos. Este tipo de servidor pode ser muito otimizado, com cache, HTTP/2, compressão, etc.
Só os utilizadores que concluem o processo de pedido são encaminhados para o "servidor de pedidos", que trata do pagamento. Este liga-se ao prestador de pagamentos. Quando um pagamento é concluído, o prestador de pagamentos contacta o servidor de pedidos para o informar do estado do pagamento. É algo sobre o qual queres ter controlo total (do lado do servidor), como fiz quando desenhei um sistema de pagamentos há algum tempo. A carga de trabalho restante no "servidor de pedidos" é muito menor, pois nem toda a gente faz um pedido, e grande parte do seu processamento pode ser feita em segundo plano.

Para maximizar a escalabilidade, podes facilmente juntar vários servidores (ou usar uma rede de distribuição de conteúdos) para servir os recursos estáticos. Para a app de pedidos de comida uso um "servidor de produtos" separado por restaurante. Isto dá alto desempenho e reduz os tempos de carregamento. As pessoas notam que é rápido, algumas nem acreditam que é tecnologia web, é quase mágico.
Conclusão
É possível desenhar uma aplicação escalável sem magia nem orçamentos de milhões. Só tens de pensar bem e procurar as oportunidades que surgem juntamente com os desafios.
O problema contém a solução. Lidar com muitos utilizadores em simultâneo não é só um problema: eles trazem o seu próprio poder de cálculo, é a solução! Só tens de o usar.