Escalando seu aplicativo da web: etapas básicas
Publicados: 2022-09-13Não basta criar aplicativos para o seu negócio, é preciso otimizá-los. Uma maneira eficaz é escalar. Neste artigo, você aprenderá sobre otimização de código, otimização de arquitetura e como criar aplicativos da Web escaláveis em geral.
Otimização
Gearheart sugere fazer a si mesmo as seguintes perguntas:
- as consultas ao banco de dados são ótimas (análise EXPLAIN, uso de índices)?
- os dados estão armazenados corretamente (SQL vs NoSQL)?
- o cache é usado?
- nenhuma solicitação desnecessária ao FS ou ao banco de dados?
- os algoritmos de processamento de dados são ideais?
- as configurações de ambiente são ideais: Apache/Nginx, MySQL/PostgreSQL, PHP/Python?
Cada uma dessas questões poderia ser abordada em um artigo separado, portanto, uma consideração detalhada delas dentro da estrutura deste artigo é claramente excessiva. É importante entender que antes de começar a dimensionar um aplicativo, é altamente desejável otimizar seu trabalho o máximo possível – na verdade, é possível que nenhum dimensionamento seja necessário.
Escala
Suponha que você já otimizou seu aplicativo, mas ainda não é capaz de lidar com a carga. Nesse caso, a solução óbvia é distribuir o aplicativo entre vários hosts para aumentar o desempenho geral do aplicativo aumentando os recursos disponíveis. Essa abordagem é oficialmente chamada de “escalonamento” do aplicativo. Mais precisamente, escalabilidade é a capacidade de um sistema aumentar seu desempenho aumentando a quantidade de recursos disponíveis para ele.
Existem dois tipos de escalabilidade: vertical e horizontal. A escalabilidade vertical implica aumentar o desempenho do aplicativo adicionando recursos (CPU, memória, disco) em um nó (host). A escala horizontal é típica para aplicativos distribuídos e implica aumentar o desempenho do aplicativo adicionando outro nó.
É claro que a maneira mais fácil é uma simples atualização de hardware (processador, memória, disco) – ou seja, dimensionamento vertical. Além disso, essa abordagem não requer nenhuma modificação no aplicativo. No entanto, o dimensionamento vertical atinge rapidamente seu limite, após o qual o desenvolvedor e o administrador não têm escolha a não ser alternar para o dimensionamento horizontal do aplicativo.
Arquitetura do aplicativo
A maioria das aplicações web são distribuídas a priori, pois sua arquitetura pode ser dividida em pelo menos três camadas: web-server, lógica de negócios (aplicação), dados (banco de dados, estática).
Cada uma dessas camadas pode ser dimensionada. Portanto, se o seu sistema tiver um aplicativo e um banco de dados residindo no mesmo host, o primeiro passo definitivamente deve ser separá-los em hosts diferentes.
O gargalo
Procedendo ao dimensionamento do sistema, a primeira coisa a fazer é determinar qual das camadas é o “gargalo”, ou seja, mais lento que o resto do sistema. Para começar, você pode usar utilitários triviais como top (htop) para avaliar o consumo de CPU/memória e df, iostat para avaliar o consumo de disco. No entanto, é desejável fornecer um host separado com uma emulação de carga de batalha (usando AB ou JMeter), na qual você pode criar o perfil do aplicativo usando utilitários como xdebug, oprofile e assim por diante. Você pode usar utilitários como o pgFouine para identificar consultas restritas ao banco de dados (claro, é melhor fazer isso com base nos logs do servidor de batalha).
Geralmente depende da arquitetura da aplicação, mas em geral os candidatos mais prováveis para um gargalo são o banco de dados e o código. Se o seu aplicativo lida com muitos dados do usuário, o gargalo provavelmente será o armazenamento estático.
Dimensionamento do banco de dados
Como mencionado acima, o gargalo em aplicativos modernos geralmente é o banco de dados. Os problemas com ele geralmente são divididos em duas classes: desempenho e necessidade de armazenar uma grande quantidade de dados.
Você pode reduzir a carga no banco de dados dividindo-o em vários hosts. Existe uma dificuldade aguda de sincronização entre eles, que pode ser resolvida implementando o esquema mestre/escravo com replicação síncrona ou assíncrona. Para PostgreSQL, você pode usar Slony-I para replicação síncrona e PgPool-II ou WAL (9.0) para replicação assíncrona. Para resolver o problema de dividir as solicitações de leitura e escrita, bem como balancear a carga entre os escravos, você pode configurar uma camada especial de acesso ao banco de dados (PgPool-II).
A preocupação de armazenar grandes quantidades de dados no caso de bancos de dados relacionais pode ser resolvida pelo particionamento (“partitioning” no PostgreSQL), ou pela implantação do banco de dados em um banco de dados distribuído como o Hadoop DFS.
Você pode ler sobre ambas as soluções no excelente livro sobre configuração do PostgreSQL.
1. No entanto, para armazenar grandes quantidades de dados, a melhor solução é o sharding, que é uma vantagem inerente à maioria dos bancos de dados NoSQL (por exemplo, MongoDB).
2. Além disso, os bancos de dados NoSQL em geral funcionam mais rápido que seus irmãos SQL devido à falta de sobrecarga para análise/otimização da consulta, verificação da integridade da estrutura de dados, etc. O tópico de comparação dos bancos de dados relacionais e NoSQL também é bastante extenso e merece um artigo separado.
3.Separadamente digna de nota é a experiência do Facebook, que usa MySQL sem seleções de JOIN. Essa estratégia permite dimensionar o banco de dados com muito mais facilidade, ao mesmo tempo em que transfere a carga do banco de dados para o código, que, como será descrito a seguir, é dimensionado com mais facilidade do que o banco de dados.
Escala de código
- As complexidades do código de dimensionamento dependem de quantos recursos compartilhados seus hosts precisam para executar seu aplicativo. Serão apenas sessões ou você precisará compartilhar caches e arquivos? De qualquer forma, a primeira coisa a fazer é executar cópias do aplicativo em vários hosts com o mesmo ambiente.
- Em seguida, você precisa configurar o balanceamento de carga/solicitação entre esses hosts. Você pode fazer isso em TCP (HAProxy), HTTP (nginx) ou DNS.
- O próximo passo, mencionado Gearheart, é disponibilizar os arquivos estáticos, cache e as sessões de aplicativos da web em cada host. Para sessões, você pode usar um servidor trabalhando na rede (por exemplo, Memcached). Como servidor de cache, faz sentido usar o mesmo Memcached, mas em um host diferente, é claro.
- Arquivos estáticos podem ser montados a partir de algum armazenamento de arquivos compartilhado via NFS/CIFS ou usando FS distribuído (HDFS, GlusterFS, Ceph).
Também é possível armazenar arquivos em um banco de dados (por exemplo, Mongo GridFS), resolvendo assim o problema de disponibilidade e escalabilidade (tendo em conta que para o banco de dados NoSQL o problema de escalabilidade é resolvido por sharding).
Separadamente digno de nota, a questão da implantação em vários hosts. Como garantir que o usuário clicando em “Atualizar” não veja versões diferentes do aplicativo? A solução mais simples, na minha opinião, seria excluir do balanceador de carga de configuração (servidor web) os hosts que não são atualizados e ativá-los sequencialmente à medida que as atualizações são feitas. Você também pode vincular usuários a hosts específicos por cookie ou IP. Se a atualização exigir alterações significativas no banco de dados, a maneira mais fácil é fechar temporariamente o projeto.