Diretrizes para testes de carga de serviços de back-end com balanceadores de carga de aplicações

Ao integrar um serviço de back-end com o Application Load Balancer, é importante medir o desempenho de um serviço de back-end por si só, na ausência de um balanceador de carga. Os testes de carga em condições controladas ajudam a avaliar as compensações do planeamento da capacidade entre diferentes dimensões do desempenho, como o débito e a latência. Uma vez que o planeamento cuidadoso da capacidade ainda pode subestimar a procura real, recomendamos que use testes de carga para determinar proativamente como a disponibilidade de um serviço é afetada quando o sistema está sobrecarregado.

Objetivos dos testes de carregamento

Um teste de carga típico mede o comportamento visível externamente do serviço de back-end em diferentes dimensões de carga. Seguem-se algumas das dimensões mais relevantes deste teste:

  • Débito de pedidos: o número de pedidos publicados por segundo.
  • Simultaneidade de pedidos: o número de pedidos processados em simultâneo.
  • Débito de ligação: o número de ligações iniciadas pelos clientes por segundo. A maioria dos serviços que usam o protocolo Transport Layer Security (TLS) tem alguma sobrecarga de transporte de rede e negociação de TLS associada a cada ligação, que é independente do processamento de pedidos.
  • Concorrência de ligações: o número de ligações de clientes processadas em simultâneo.

  • Latência do pedido: o tempo decorrido total entre o início do pedido e o fim da resposta.

  • Taxa de erros: frequência com que os pedidos causam erros, como erros HTTP 5xx e ligações fechadas prematuramente.

Para avaliar o estado do servidor sob carga, um procedimento de teste de carga também pode recolher as seguintes métricas de serviço internas:

  • Utilização de recursos do sistema: os recursos do sistema, como a CPU, a RAM e os identificadores de ficheiros (sockets), são normalmente expressos em percentagem.

    A importância destas métricas difere consoante a forma como o serviço é implementado. As aplicações têm um desempenho reduzido, perdem carga ou falham quando esgotam os respetivos recursos. Por conseguinte, torna-se essencial determinar a disponibilidade de recursos quando um anfitrião está sob carga elevada.

  • Utilização de outros recursos limitados: recursos não pertencentes ao sistema que podem ficar esgotados sob carga, como na camada de aplicação.

    Alguns exemplos de tais recursos incluem o seguinte:

    • Um conjunto limitado de processos ou threads de trabalho.
    • Para um servidor de aplicações que usa threads, é comum limitar o número de threads de trabalho em funcionamento em simultâneo. Os limites do tamanho do conjunto de threads são úteis para evitar o esgotamento da memória e da CPU, mas as predefinições são frequentemente muito conservadoras. Os limites demasiado baixos podem impedir a utilização adequada dos recursos do sistema.
    • Alguns servidores usam conjuntos de processos em vez de conjuntos de threads. Por exemplo, um servidor Apache, quando configurado com o modelo de multiprocessamento Prefork, atribui um processo a cada ligação de cliente. Assim, o limite de tamanho do conjunto determina o limite superior da simultaneidade de ligações.
    • Um serviço implementado como um frontend para outro serviço que tem um conjunto de ligações de backend de tamanho limitado.

Planeamento de capacidade versus testes de sobrecarga

As ferramentas de teste de carga ajudam a medir diferentes dimensões de escalabilidade individualmente. Para o planeamento da capacidade, determine o limite de carga para o desempenho aceitável em várias dimensões. Por exemplo, em vez de medir o limite absoluto de um pedido de serviço, considere medir o seguinte:

  • A taxa de pedidos à qual o serviço pode publicar com uma latência no percentil 99 inferior a um número especificado de milissegundos. O número é especificado pelo SLO do serviço.
  • A taxa de pedidos máxima que não faz com que a utilização dos recursos do sistema exceda os níveis ideais. Tenha em atenção que a utilização ideal varia consoante a aplicação e pode ser significativamente inferior a 100%. Por exemplo, com uma utilização de memória máxima de 80%, a aplicação pode conseguir processar picos de carga menores melhor do que se a utilização máxima fosse de 99%.

Embora seja importante usar os resultados dos testes de carga para tomar decisões de planeamento de capacidade, é igualmente importante compreender o comportamento de um serviço quando a carga excede a capacidade. Seguem-se alguns comportamentos do servidor que são frequentemente avaliados através de testes de sobrecarga:

  • Redução de carga: quando um serviço recebe um número excessivo de pedidos ou ligações de entrada, pode responder abrandando todos os pedidos ou rejeitando alguns pedidos para manter um desempenho aceitável para os restantes. Recomendamos a última abordagem para evitar limites de tempo do cliente antes de receber uma resposta e para reduzir o risco de esgotamento da memória diminuindo a concorrência de pedidos no servidor.

  • Resiliência contra o esgotamento de recursos: geralmente, um serviço evita falhas devido ao esgotamento de recursos, uma vez que é difícil para os pedidos pendentes progredirem se o serviço tiver falhado. Se um serviço de back-end tiver muitas instâncias, a robustez das instâncias individuais é vital para a disponibilidade geral do serviço. Enquanto uma instância é reiniciada após uma falha, outras instâncias podem sofrer uma carga maior, o que pode causar uma falha em cascata.

Diretrizes gerais de testes

Ao definir os seus exemplos de teste, tenha em atenção as seguintes diretrizes.

Crie testes em pequena escala

Crie testes em pequena escala para medir os limites de desempenho do servidor. Com uma capacidade excessiva do servidor, existe o risco de um teste não revelar os limites de desempenho do próprio serviço, mas pode descobrir gargalos noutros sistemas, como os anfitriões do cliente ou a camada de rede.

Para obter os melhores resultados, considere um teste que use uma única instância de máquina virtual (VM) ou um pod do Google Kubernetes Engine (GKE) para testar o serviço de forma independente. Para alcançar a carga total no servidor, se necessário, pode usar várias VMs, mas lembre-se de que podem complicar a recolha de dados de desempenho.

Escolha padrões de carga de ciclo aberto

A maioria dos geradores de carga usa o padrão de ciclo fechado para limitar o número de pedidos concorrentes e atrasar novos pedidos até que os anteriores estejam concluídos. Não recomendamos esta abordagem porque os clientes de produção do serviço podem não apresentar este comportamento de limitação.

Por outro lado, o padrão de ciclo aberto permite que os geradores de carga simulem a carga de produção enviando pedidos a uma taxa constante, independentemente da taxa à qual as respostas do servidor chegam.

Recomendamos os seguintes geradores de carga para o teste de carga do serviço de back-end:

Nighthawk

O Nighthawk é uma ferramenta de código aberto desenvolvida em coordenação com o projeto Envoy. Pode usá-lo para gerar carga do cliente, visualizar referências e medir o desempenho do servidor para a maioria dos cenários de teste de carga de serviços HTTPS.

Teste HTTP/1

Para testar o HTTP/1, use o seguinte comando:

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http1 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --connections CONNECTIONS

Substitua o seguinte:

  • URI: o URI a comparar com a referência
  • DURATION: tempo total de execução do teste em segundos
  • REQ_BODY_SIZE: tamanho do payload POST em cada pedido
  • CONCURRENCY: o número total de ciclos de eventos concorrentes

    Este número deve corresponder à contagem de núcleos da VM do cliente

  • RPS: a taxa alvo de pedidos por segundo, por ciclo de eventos

  • CONNECTIONS: o número de ligações simultâneas, por ciclo de eventos

Veja o exemplo seguinte:

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http1 --request-body-size 5000 \
    --concurrency 16 --rps 500 --connections 200

O resultado de cada execução de teste fornece um histograma das latências de resposta. No exemplo da documentação do Nighthawk , repare que a latência do percentil 99 é de aproximadamente 135 microssegundos.

Initiation to completion
    samples: 9992
    mean:    0s 000ms 113us
    pstdev:  0s 000ms 061us

    Percentile  Count       Latency
    0           1           0s 000ms 077us
    0.5         4996        0s 000ms 115us
    0.75        7495        0s 000ms 118us
    0.8         7998        0s 000ms 118us
    0.9         8993        0s 000ms 121us
    0.95        9493        0s 000ms 124us
    0.990625    9899        0s 000ms 135us
    0.999023    9983        0s 000ms 588us
    1           9992        0s 004ms 090us

Teste o HTTP/2

Para testar o HTTP/2, use o seguinte comando:

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http2 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --max-active-requests MAX_ACTIVE_REQUESTS \
    --max-concurrent-streams MAX_CONCURRENT_STREAMS

Substitua o seguinte:

  • URI: o URI a comparar com a referência
  • DURATION: tempo total de execução do teste em segundos
  • REQ_BODY_SIZE: tamanho do payload POST em cada pedido
  • CONCURRENCY: o número total de ciclos de eventos concorrentes

    Este número deve corresponder à contagem de núcleos da VM do cliente

  • RPS: a taxa alvo de pedidos por segundo para cada ciclo de eventos

  • MAX_ACTIVE_REQUESTS: o número máximo de pedidos ativos concorrentes para cada ciclo de eventos

  • MAX_CONCURRENT_STREAMS: o número máximo de streams simultâneas permitidas em cada ligação HTTP/2

Veja o exemplo seguinte:

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http2 --request-body-size 5000 \
    --concurrency 16 --rps 500 \
    --max-active-requests 200 --max-concurrent-streams 1

ab (ferramenta de referência Apache)

ab é uma alternativa menos flexível ao Nighthawk, mas está disponível como um pacote em quase todas as distribuições do Linux. ab só é recomendado para testes rápidos e simples.

Para instalar o ab, use o seguinte comando:

  • No Debian e Ubuntu, execute sudo apt-get install apache2-utils.
  • Em distribuições baseadas no RedHat, execute sudo yum install httpd-utils.

Depois de instalar o ab, use o seguinte comando para o executar:

ab -c CONCURRENCY \
    -n NUM_REQUESTS \
    -t TIMELIMIT \
    -p POST_FILE URI

Substitua o seguinte:

  • CONCURRENCY: número de pedidos simultâneos a executar
  • NUM_REQUESTS: número de pedidos a executar
  • TIMELIMIT: número máximo de segundos a gastar em pedidos
  • POST_FILE: ficheiro local que contém a carga útil HTTP POST
  • URI: o URI a comparar com a referência

Veja o exemplo seguinte:

ab -c 200 -n 1000000 -t 600 -p body http://10.20.30.40:80

O comando no exemplo anterior envia pedidos com uma concorrência de 200 (padrão de ciclo fechado) e para após 1 000 000 (um milhão) de pedidos ou 600 segundos de tempo decorrido. O comando também inclui o conteúdo do ficheiro body como uma carga útil HTTP POST.

O comando ab produz histogramas de latência de resposta semelhantes aos do Nighthawk, mas a respetiva resolução está limitada a milissegundos, em vez de microsegundos:

Percentage of the requests served within a certain time (ms)
    50%     7
    66%     7
    75%     7
    80%     7
    90%    92
    95%   121
    98%   123
    99%   127
    100%  156 (longest request)

O que se segue?