---
title: "Design de aplicações escaláveis sem magia"
date: 2020-05-11
language: pt
topics: ["cloud", "data", "design", "howto", "iOS", "lemmid-store", "programming", "server", "smartphone", "web", "webdesign"]
source_url: https://willem.com/pt/2020-05-11_design-de-aplicacoes-escalaveis-sem-magia/
---
# Design de aplicações escaláveis sem magia

*Aproveitar o poder de cálculo do cliente para alto desempenho com muitos utilizadores*

> 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?

## 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!)*

![Um utilizador a interagir com um servidor](https://willem.com/global/images/02bda6e71f78.webp)

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 (https://willem.com/en/2020-03-03_monolithic-vs-microservices-software-architecture/).

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.

![Servidor sob carga elevada a tratar de vários utilizadores em simultâneo](https://willem.com/global/images/0983daa5d3fa.webp)

Por causa do design_monolítico_da_app (https://willem.com/en/2020-03-03_monolithic-vs-microservices-software-architecture/), 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".

![Os smartphones (e tablets, PC) tornaram-se mais poderosos ao longo dos anos (imagem: Apple)](https://willem.com/global/images/e4d580ef3ee0.webp)

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.

![Aproveitar o dispositivo do utilizador: o telefone do utilizador faz a maior parte do trabalho que originalmente era do servidor](https://willem.com/global/images/3b24ef5be7d6.webp)

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 (https://willem.com/en/2020-05-01_designing-an-interface-for-a-food-ordering-page/) 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.

![Pedir comida online](https://willem.com/global/images/d08b396a761f.webp)

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.

![Separar a carga de trabalho restante do servidor: servir produtos e tratar de pedidos](https://willem.com/global/images/1d2831da7a52.webp)

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 (https://willem.com/en/2020-03-25_designing-and-implementing-a-micro-payment-system/) 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.

![Lidar com muito tráfego através de computação distribuída](https://willem.com/global/images/4c106f30570a.webp)

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.
