Otimização do ajuste de escala automático de serviços do Amazon ECS
Um serviço do Amazon ECS é uma coleção gerenciada de tarefas. Cada serviço tem uma definição de tarefa associada, uma contagem de tarefas desejada e uma estratégia de posicionamento opcional.
O ajuste de escala automático de serviços do Amazon ECS funciona por meio do serviço Application Auto Scaling. O Application Auto Scaling usa métricas do CloudWatch como fonte para métricas de escalabilidade. Ele também usa os alarmes do CloudWatch para definir limites sobre quando aumentar ou reduzir horizontalmente a escala do serviço.
Você fornece os limites para escalabilidade. Você pode definir uma meta de métrica, chamada de escala de rastreamento de metas. Você também pode especificar limites, o que é chamado de escalabilidade de etapas.
Depois que o Application Auto Scaling é configurado, ele calcula continuamente a contagem adequada de tarefas desejadas para o serviço. Ele também notifica o Amazon ECS quando a contagem de tarefas desejada deve mudar, seja aumentando ou reduzindo a escala horizontalmente.
Para usar o ajuste de escala automático do serviço de forma eficaz, você deve escolher uma métrica de escalabilidade apropriada. Vamos abordamos como escolher uma métrica nas seções a seguir.
Caracterização da aplicação
A escalabilidade adequada de uma aplicação exige que você conheça as condições em que você deve reduzir e aumentar a escala horizontalmente dela.
Em essência, você deverá aumentar a escala horizontalmente se houver previsão de que a demanda vá ultrapassar a capacidade. Por outro lado, você poderá reduzir a escala horizontalmente da aplicação para conservar os custos quando os recursos excederem a demanda.
Identificação de uma métrica de utilização
Para escalar com eficácia, você deve identificar uma métrica que indique a utilização ou saturação. Essa métrica deve exibir as propriedades a seguir para ser útil ao escalar.
-
A métrica deve estar correlacionada com a demanda. Quando os recursos são mantidos estáveis, mas a demanda muda, o valor da métrica também deve mudar. A métrica deve aumentar ou diminuir quando a demanda aumenta ou diminui.
-
O valor da métrica deve reduzir a escala horizontalmente de modo proporcional à capacidade. Quando a demanda se mantém constante, a adição de mais recursos deve resultar em uma mudança proporcional no valor da métrica. Portanto, dobrar o número de tarefas deve fazer com que a métrica diminua em 50%.
A melhor maneira de identificar uma métrica de utilização é por meio de testes de carga em um ambiente de pré-produção, como um ambiente de teste. Soluções de teste de carga comerciais e de código aberto estão amplamente disponíveis. Normalmente, essas soluções podem gerar carga sintética ou simular tráfego real de usuários.
Para iniciar o processo de teste de carga, você deve primeiro criar painéis para as métricas de utilização da aplicação. Essas métricas incluem utilização da CPU, utilização da memória, operações de E/S, profundidade da fila de E/S e throughput da rede. Você pode coletar essas métricas com um serviço, como o CloudWatch Container Insights. Também é possível coletá-las usando o Amazon Managed Service for Prometheus junto com o Amazon Managed Grafana. Durante esse processo, certifique-se de coletar e traçar métricas para os tempos de resposta da aplicação ou para as taxas de conclusão do trabalho.
Ao testar a carga, comece com uma pequena taxa de solicitação ou inserção de trabalho. Mantenha essa taxa estável por alguns minutos para permitir o aquecimento da aplicação. Em seguida, aumente lentamente a taxa e mantenha-a estável por alguns minutos. Repita esse ciclo, aumentando a taxa a cada vez, até que os tempos de resposta ou conclusão da aplicação estejam muito lentos para atender aos objetivos de nível de serviço (SLOs).
Durante o teste de carga, examine cada uma das métricas de utilização. As métricas que aumentam com a carga são as principais candidatas a melhores métricas de utilização.
Em seguida, identifique o recurso que atinge a saturação. Ao mesmo tempo, também examine as métricas de utilização para ver qual delas se estabiliza primeiro em um nível alto. Ou examine qual delas atinge o pico e, em seguida, trava sua aplicação primeiro. Por exemplo, se a utilização da CPU aumentar de 0% para 70% a 80% à medida que você adiciona carga, e permanecer nesse nível após adicionar ainda mais carga, é seguro dizer que a CPU está saturada. Dependendo da arquitetura da CPU, talvez nunca chegue a 100%. Por exemplo, suponha que a utilização da memória aumente à medida que você adiciona carga e, em seguida, a aplicação trava repentinamente ao atingir o limite de memória da tarefa ou da instância do Amazon EC2. Nessa situação, é provável que a memória tenha sido totalmente consumida. Vários recursos podem ser consumidos pela aplicação. Portanto, escolha a métrica que representa o recurso que se esgota primeiro.
Por fim, tente testar a carga novamente depois de dobrar o número de tarefas ou de instâncias do Amazon EC2. Suponha que a métrica principal aumente ou diminua pela metade da taxa de antes. Se for esse o caso, a métrica é proporcional à capacidade. Essa é uma boa métrica de utilização para ajuste de escala automático.
Agora, considere esse cenário hipotético. Suponha que você teste a carga de uma aplicação e descubra que a utilização da CPU chega a 80% com 100 solicitações por segundo. Quando mais carga é adicionada, a utilização da CPU não aumenta mais. No entanto, isso faz com que a aplicação responda mais lentamente. Em seguida, você executa o teste de carga novamente, dobrando o número de tarefas, mas mantendo a taxa no valor de pico anterior. Se você descobrir que a utilização média da CPU cai para cerca de 40%, a utilização média da CPU é uma boa candidata para uma métrica de escalabilidade. Por outro lado, se a utilização da CPU permanecer em 80% após aumentar o número de tarefas, a utilização média da CPU não é uma boa métrica de escalabilidade. Nesse caso, você precisa fazer mais pesquisa para encontrar uma métrica adequada.
Modelos de aplicações e propriedades de escala comuns
É possível executar softwares de todos os tipos na AWS. Muitas workloads são desenvolvidas internamente, enquanto outras são baseadas em softwares populares de código aberto. Independentemente de sua origem, observamos alguns padrões comuns de design de serviços. A forma eficaz de escalar depende, em grande parte, do padrão.
O servidor eficiente vinculado à CPU
O servidor eficiente vinculado à CPU não utiliza quase nenhum recurso além da CPU e do throughput da rede. Cada solicitação pode ser processada apenas pela aplicação. As solicitações não dependem de outros serviços, como bancos de dados. A aplicação pode processar centenas de milhares de solicitações simultâneas e utilizar com eficiência várias CPUs para isso. Cada solicitação é atendida por um thread dedicado com pouca sobrecarga de memória ou há um loop de eventos assíncrono executado em cada CPU que atende às solicitações. Cada réplica da aplicação é igualmente capaz de processar uma solicitação. O único recurso que pode ser esgotado antes da CPU é a largura de banda da rede. Em serviços vinculados à CPU, a utilização da memória, mesmo no pico de throughput, é uma fração dos recursos disponíveis.
Você pode usar o ajuste de escala automático baseado em CPU para esse tipo de aplicação. A aplicação desfruta da máxima flexibilidade em termos de escala. Ela pode ser escala verticalmente, o que fornece à aplicação instâncias maiores do Amazon EC2 ou vCPUs do Fargate. Além disso, ela também pode ser escalada horizontalmente adicionando mais réplicas. Adicionar réplicas ou dobrar o tamanho da instância reduz pela metade a utilização média da CPU em relação à capacidade.
Se estiver usando a capacidade do Amazon EC2 nessa aplicação, considere colocá-la em instâncias otimizadas para computação, como a família c5
ou c6g
.
O servidor eficiente vinculado à memória
O servidor eficiente vinculado à memória aloca uma quantidade significativa de memória por solicitação. Na máxima simultaneidade, mas não necessariamente no throughput, a memória se esgota antes dos recursos da CPU. A memória associada a uma solicitação é liberada quando a solicitação se encerra. Solicitações adicionais podem ser aceitas desde que haja memória disponível.
Você pode usar ajuste de escala automático baseado em memória para esse tipo de aplicação. A aplicação desfruta da máxima flexibilidade em termos de escala. Você pode escalá-la verticalmente, fornecendo a ela maiores recursos de memória do Amazon EC2 ou do Fargate. Além disso, ela também pode ser escalada horizontalmente adicionando mais réplicas. Adicionar réplicas ou dobrar o tamanho da instância pode reduzir pela metade a utilização média da memória em relação à capacidade.
Se estiver usando a capacidade do Amazon EC2 nessa aplicação, considere colocá-la em instâncias otimizadas para memória, como a família r5
ou r6g
.
Algumas aplicações vinculadas à memória não liberam a memória associada a uma solicitação quando ela se encerra, de modo que uma redução na simultaneidade não resulta em uma redução na memória usada. Para isso, não recomendamos usar a escalabilidade baseada em memória.
O servidor baseado em operadores
O servidor baseado em operadores processa uma solicitação para cada thread de operador individual, uma após a outra. Os threads de operador podem ser leves, como os threads POSIX. Eles também podem ser mais pesados, como os processos UNIX. Não importa qual thread eles sejam, sempre há uma simultaneidade máxima que a aplicação pode suportar. Normalmente, o limite de simultaneidade é definido proporcionalmente aos recursos de memória disponíveis. Se o limite de simultaneidade for atingido, a aplicação colocará solicitações adicionais em uma fila de backlog. Se a fila de backlog transbordar, solicitações adicionais de entrada serão imediatamente rejeitadas. As aplicações comuns que se encaixam nesse padrão incluem o servidor Web Apache e o Gunicorn.
A simultaneidade de solicitações costuma ser a melhor métrica para escalar essa aplicação. Como há um limite de simultaneidade para cada réplica, é importante aumentar a escala horizontalmente antes que o limite médio seja atingido.
A melhor maneira de obter métricas de simultaneidade de solicitações é fazer com que a aplicação as relate ao CloudWatch. Cada réplica da aplicação pode publicar o número de solicitações simultâneas como uma métrica personalizada em alta frequência. Recomendamos que a frequência seja definida para, pelo menos, uma vez a cada minuto. Depois que vários relatórios são coletados, você pode usar a simultaneidade média como métrica de escalabilidade. Essa métrica é calculada pegando a simultaneidade total e dividindo-a pelo número de réplicas. Por exemplo, se a simultaneidade total for 1.000 e o número de réplicas for 10, a simultaneidade média será 100.
Se a aplicação estiver por trás de um Application Load Balancer, você também pode usar a métrica ActiveConnectionCount
do balanceador de carga como fator na métrica de escalabilidade. A métrica ActiveConnectionCount
deve ser dividida pelo número de réplicas para obter um valor médio. O valor médio deve ser usado para ajuste de escala, em oposição ao valor bruto da contagem.
Para que esse projeto funcione melhor, o desvio padrão da latência de resposta deve ser pequeno em taxas de solicitação baixas. Recomendamos que, durante períodos de baixa demanda, a maioria das solicitações seja respondida em pouco tempo e que não haja muitas solicitações que demorem muito mais do que o tempo médio para serem respondidas. O tempo médio de resposta deve estar próximo ao tempo de resposta do 95º percentil. Caso contrário, podem ocorrer transbordamos de fila como resultado. Isso leva a erros. Recomendamos que você forneça réplicas adicionais quando necessário para reduzir o risco de transbordamento.
O servidor em espera
O servidor em espera realiza algum processamento para cada solicitação, mas dependente muito de um ou mais serviços downstream para funcionar. As aplicações de contêiner costumam fazer uso intenso de serviços downstream, como bancos de dados e outros serviços de API. Pode levar algum tempo para que esses serviços respondam, principalmente em cenários de alta capacidade ou alta simultaneidade. Isso ocorre porque essas aplicações tendem a usar poucos recursos da CPU e a utilizar sua máxima simultaneidade em termos de memória disponível.
O serviço em espera é adequado tanto no padrão de servidor vinculado à memória quanto no padrão de servidor baseado em operadores, dependendo de como a aplicação foi projetada. Se a simultaneidade da aplicação for limitada somente pela memória, a utilização média da memória deve ser usada como métrica de escalabilidade. Se a simultaneidade da aplicação for baseada em um limite de operadores, a simultaneidade média deve ser usada como uma métrica de escalabilidade.
O servidor baseado em Java
Se o servidor baseado em Java estiver vinculado à CPU e escalar proporcionalmente aos recursos da CPU, ele pode ser adequado para o padrão eficiente de servidor vinculado à CPU. Se for esse o caso, a utilização média da CPU pode ser apropriada como métrica de escalabilidade. No entanto, muitas aplicações Java não são vinculadas à CPU, o que as torna difíceis de escalar.
Para obter o melhor desempenho, recomendamos alocar o máximo de memória possível no heap da Java Virtual Machine (JVM). Versões recentes da JVM, incluindo a atualização 191 ou posterior do Java 8, definem automaticamente o tamanho do heap o maior possível para caber no contêiner. Isso significa que, em Java, a utilização da memória raramente é proporcional à utilização da aplicação. À medida que a taxa de solicitações e a simultaneidade aumentam, a utilização da memória permanece constante. Por isso, não recomendamos escalar servidores baseados em Java com base na utilização da memória. Em vez disso, recomendamos escalar a utilização da CPU.
Em alguns casos, os servidores baseados em Java esgotam o heap antes de esgotar a CPU. Se a aplicação for propensa ao esgotamento de heap com alta simultaneidade, as conexões médias são a melhor métrica de escalabilidade. Se a aplicação for propensa ao esgotamento de heap com alto throughput, a taxa média de solicitações é a melhor métrica de escalabilidade.
Servidores que usam outros runtimes com coleta de resíduos
Muitas aplicações de servidor são baseadas em runtimes que realizam coleta de resíduos, como .NET e Ruby. Essas aplicações de servidor podem se encaixar em um dos padrões descritos anteriormente. No entanto, como acontece em Java, não recomendamos escalar essas aplicações com base na memória, porque a utilização média de memória observada não costuma estar correlacionada ao throughput ou à simultaneidade.
Para essas aplicações, recomendamos escalar a utilização da CPU se a aplicação estiver vinculada à CPU. Caso contrário, recomendamos escalar o throughput médio ou a simultaneidade média, com base nos resultados do teste de carga.
Processadores de trabalhos
Muitas workloads envolvem o processamento assíncrono de trabalhos. Elas incluem aplicações que não recebem solicitações em tempo real, mas se inscrevem em uma fila de trabalho para receber tarefas. Para esses tipos de aplicações, a métrica de escalabilidade adequada é quase sempre a profundidade da fila. O crescimento da fila é uma indicação de que o trabalho pendente supera a capacidade de processamento, enquanto uma fila vazia indica que há mais capacidade do que trabalho a ser feito.
Serviços de mensagens da AWS, como Amazon SQS e Amazon Kinesis Data Streams, fornecem métricas do CloudWatch que podem ser usadas para escalar. No Amazon SQS, ApproximateNumberOfMessagesVisible
é a melhor métrica. No Kinesis Data Streams, considere usar a métrica MillisBehindLatest
, publicada pela Kinesis Client Library (KCL). Essa métrica deve ser calculada em média entre todos os consumidores antes de usá-la para escalar.