Systems Performance - Capacity Planning
Estratégias para planejar recursos com máxima eficiência sem quebrar o orçamento
Bem vindos a mais um post da série sobre Systems Performance. Agora vou abordar um tema que vejo que é pouco discutido em times de engenharia e SREs, mas super importante. Capacity Planning.
Capacity Planning sempre existiu, não é coisa nova. Nos meus tempos de sysadmin e datacenters físicos, planejar a capacidade dos recursos computacionais era super importante pois nos dava uma visão de que tipo de servidores comprar, se seria necessário investir em novos switches de fibra para interligar aquele novo storage lotado de disco SSD e por aí vai. Era uma época onde escalar horizontalmente não era tão simples (e barato) como hoje, então tudo devia ser muito bem pensado.
Com avanço da cloud, ficou muito mais fácil de escalar, subir e descer recursos sob demanda, mas assim como no passado (não tão distante), economia de custos sempre foi uma vantagem muito competitiva. E Capacity Planning é isso, é ter orçado e planejado a quantidade certa de recursos para desde uma aplicação até toda camada de dados e armazenamento. É saber colocar na balança performance e custo, sem levar a empresa a falência. É o famoso fazer mais com menos.
Capacity Planning também é analisar como uma aplicação vai se comportar quando começar a receber requisições e como ela irá escalar conforme a quantidade de requisições aumenta. Essa análise pode ser feita de várias maneiras, vamos lá.
Limites de Recursos
Encontrar o recurso que vai se tornar o gargalo sob muita carga é a primeira forma de análise. Em ambientes de containers, recursos podem ter seus limites setados por configurações e que podem vir a se tornar um gargalo. E por onde podemos começar?
Comece identificado o papel daquele servidor e o tipo de requisições que ele serve, por exemplo, um servidor web serve requisições HTTP, já um servidor NFS serve requisições através do protocolo NFS e um servidor de banco de dados serve requisições de queries.
O próximo passo é determinar o consumo de recurso por requisição. Para um sistema existente, você pode medir a taxa de requisições atuais junto com a utilização dos recursos, onde você pode estimar através de extrapolação qual recurso irá chegar aos 100% de utilização primeiro e qual será a taxa de requests. Para sistemas que estão sendo desenvolvidos, você pode utilizar ferramentas que de load test para simular a quantidade de requests que aquele sistema deve receber e ao mesmo tempo medindo a utilização dos recursos. Conforme o número de requisições for aumentando, com certeza você irá encontrar o limite de utilização de cada recurso.
Certo, mas quais recursos eu posso monitorar?
Para hardware, utilização de CPU, utilização de memória, IOPS, throughput do disco e rede e espaço em disco. No lado do software, utilização da memória virtual, quantidade de threads/processos e de file descriptors.
Digamos que você está analisando uma aplicação que está recebendo 200 requests por segundo e o recurso mais ocupado no seu servidor são as 8 CPUs que ele possui, que estão com utilização média de 60%, então você chega na conclusão que esse será o seu gargalo assim que a CPU chegar em 100%, mas como você vai precisar saber quantas requests por segundo o sistema precisa receber pra chegar a esse ponto, certo? A fórmula pra chegar no valor é basicamente:
A predição seria de aproximadamente 333 requests por segundo para a CPU chegar a 100% ocupada. Porém, vale lembrar que essa é uma estimativa, pode ser que durante o aumento de requests algum outro fator limitante possa ser encontrado.
Análise de Recursos
Ao escolher uma instância/VM na cloud ou adquirir novo hardware há alguns fatores que podem ser alterados para alcançar a performance desejada. Esse processo inclui escolher o tipo e tamanho de discos e CPUs, a quantidade de memória, configurações do filesystem e por aí vai. A ideia é conseguir a performance desejada com o menor custo possível.
Uma ideia de como testar combinações de configurações a partir de uma configuração “máxima”:
Teste a performance usando todos os recursos configuradas ao máximo, por exemplo: uma instância com 16 CPUs, 64GB de RAM, utilizando discos SSD ou NVMe formatados e particionados da melhor forma possível;
Mude os fatores um por um e continue testando a performance;
Anote a porcentagem de queda de performance para cada recurso alterado e o custo financeiro de cada alteração/configuração;
Iniciar com o máximo de performance (e que o bolso pode pagar), escolha os recursos que você abre mão para economizar mais, ao mesmo tempo mantendo a quantidade de requests necessárias;
Teste novamente a configuração ideal para confirmar se a performance está dentro do esperado.
Imagine que estamos projetando a capacidade de um banco de dados distribuído para uma aplicação de recomendação de produtos. O requisito é suportar 500k queries por segundo, com um dataset de 2TB, distribuído em 10 nodes. A configuração máxima para cada node seria 2 CPUs com 32 cores, 512GB de RAM, 4 SSDs entregando 100k IOPS cada e um throughput de rede de 40Gbps.
Se reduzirmos os recursos, vamos observar as seguintes quedas na performance:
Reduzindo para 1 CPU: 45%
Utilizando metade de RAM (256GB): 60%
Usando 2 SSDs: 35%
Reduzindo a rede para 10Gbps: 30%
Com essa configuração, vamos estimar a performance estimada por cada node:
Cada node entregaria ~5000 queries por segundo, ou seja para suportar 500k queries por segundo, precisaríamos de 100 nodes. Analisando profundamente, essa configuração “econômica” pode não ser viável. Por isso, teste muito e avalie o que vale a pena no fim do dia.
Escalando
Quando pensamos em escalar sistemas, geralmente pensamos em duas soluções, a vertical, onde aumentamos a quantidade de recurso de um determinado servidor, ou a horizontal, onde aumentamos a quantidade de réplicas geralmente atrás de um load balancer para que esses servidores pareçam ser um só.
Hoje quando só falamos em cloud, é possível utilizar instâncias/VMs menores e se aproveitar cada vez mais do escalonamento horizontal de forma mais eficiente, sempre adicionando novas instâncias aos poucos, o que nos leva a um Capacity Planning menos “rigoroso” no início de um projeto em fase de desenvolvimento.
Como já bem conhecido e estabelecido é possível automatizar esse escalonamento em qualquer cloud utilizando métricas. Por exemplo, na AWS existe o Auto Scaling Group, onde uma política de escalonamento customizado pode ser utilizada para aumentar ou diminuir a quantidade de instâncias baseada em uma métrica.
Em sistemas como o Kubernetes, também existe suporte a autoscaling, como por exemplo o HPA, que pode escalar Pods de forma horizontal se baseando em métricas de utilização de CPU ou de alguma outra métrica customizada.
Já para bancos de dados, uma forma comum de escalonamento é o sharding, onde o dado é dividido em segmentos lógicos gerenciado por instâncias distintas.
Pretendo fazer um post sobre isso mais para frente.
Pontos finais
Capacity Planning sempre existiu e sempre irá existir como uma boa prática, não só para SREs, mas para arquitetos de soluções, engenheiros trabalhando em early-stage Startups ou Scaleups. É uma estratégia bem importante para definir como crescer e escalar seus sistemas.
Até a próxima!