Linee guida per i servizi di backend dei test di carico con bilanciatori del carico delle applicazioni

Durante l'integrazione di un servizio di backend con l'Application Load Balancer, è importante misurare le prestazioni di un servizio di backend autonomamente, in assenza di un bilanciatore del carico. I test di carico in condizioni controllate consentono di valutare i compromessi della pianificazione della capacità tra le diverse dimensioni delle prestazioni, come velocità effettiva e latenza. Poiché un'attenta pianificazione della capacità potrebbe comunque sottostimare la domanda effettiva, ti consigliamo di utilizzare i test di carico per determinare in modo proattivo in che modo la disponibilità di un servizio viene influenzata quando il sistema è sovraccarico.

Obiettivi del test di carico

Un tipico test di carico misura il comportamento visibile esternamente del servizio di backend in diverse dimensioni di carico. Alcune delle dimensioni più rilevanti di questo test sono le seguenti:

  • Velocità effettiva richiesta:il numero di richieste gestite al secondo.
  • Contemporaneità delle richieste:il numero di richieste elaborate contemporaneamente.
  • Velocità effettiva di connessione: il numero di connessioni avviate dai client al secondo. La maggior parte dei servizi che utilizzano TLS (Transport Layer Security) prevede un overhead per il trasporto di rete e la negoziazione TLS associato a ogni connessione, indipendente dall'elaborazione delle richieste.
  • Contemporaneità connessioni: il numero di connessioni client elaborate contemporaneamente.

  • Latenza della richiesta: il tempo totale trascorso tra l'inizio della richiesta e la fine della risposta.

  • Percentuale di errore: la frequenza con cui le richieste causano errori, come errori HTTP 5xx e connessioni chiuse prematuramente.

Per valutare l'integrità del server sotto carico, una procedura di test di carico potrebbe anche raccogliere le seguenti metriche di servizio interne:

  • Utilizzo di risorse di sistema: le risorse di sistema, come CPU, RAM e handle di file (socket), sono generalmente espresse in percentuale.

    L'importanza di queste metriche varia in base a come viene implementato il servizio. Le prestazioni delle applicazioni si riducono, eliminano il carico o si arrestano in modo anomalo quando esauriscono le risorse. Di conseguenza, diventa essenziale determinare la disponibilità delle risorse quando un host è sottoposto a un carico elevato.

  • Uso di altre risorse limitate: risorse non di sistema che potrebbero esaurirsi sotto carico, ad esempio a livello di applicazione.

    Ecco alcuni esempi di risorse di questo tipo:

    • Un pool limitato di thread o processi worker.
    • Per un server delle applicazioni che utilizza i thread, è comune limitare il numero di thread di lavoro contemporaneamente. I limiti di dimensione del pool di Thread sono utili per evitare l'esaurimento di memoria e CPU, ma le impostazioni predefinite sono spesso molto prudenti. Limiti troppo bassi potrebbero impedire un uso adeguato delle risorse di sistema.
    • Alcuni server utilizzano pool di processi anziché pool di thread. Ad esempio, un server Apache, se configurato con il modello di elaborazione multipla Prefork, assegna un processo a ogni connessione client. Il limite di dimensioni del pool determina quindi il limite superiore sulla contemporaneità delle connessioni.
    • Un servizio di cui è stato eseguito il deployment come frontend a un altro servizio con un pool di connessioni backend di dimensioni limitate.

Confronto tra pianificazione della capacità e test di sovraccarico

Gli strumenti di test del carico consentono di misurare singolarmente le diverse dimensioni di scalabilità. Per la pianificazione della capacità, determina la soglia di carico per le prestazioni accettabili in più dimensioni. Ad esempio, anziché misurare il limite assoluto di una richiesta di servizio durante l'intero processo, ti consigliamo di misurare quanto segue:

  • La tasso di richieste con cui il servizio può gestire con una latenza al 99° percentile inferiore a un numero specificato di millisecondi. Il numero è specificato dallo SLO del servizio.
  • La frequenza massima di richieste che non causa il superamento dei livelli ottimali da parte dell'utilizzo delle risorse di sistema. Tieni presente che l'utilizzo ottimale varia a seconda dell'applicazione e potrebbe essere notevolmente inferiore al 100%. Ad esempio, con un picco di utilizzo della memoria dell'80%, l'applicazione potrebbe essere in grado di gestire picchi di carico minori rispetto a quando l'utilizzo massimo fosse del 99%.

Sebbene sia importante utilizzare i risultati dei test di carico per prendere decisioni di pianificazione della capacità, è altrettanto importante comprendere come si comporta un servizio quando il carico supera la capacità. Di seguito sono riportati alcuni comportamenti del server che vengono spesso valutati mediante test di sovraccarico:

  • Riduzione del carico: quando un servizio riceve un numero eccessivo di richieste o connessioni in entrata, può rispondere rallentando tutte le richieste o rifiutando alcune richieste per mantenere prestazioni accettabili per quelle rimanenti. Consigliamo quest'ultimo approccio per evitare i timeout del client prima di ricevere una risposta e per ridurre il rischio di esaurimento della memoria riducendo la contemporaneità delle richieste sul server.

  • Resistenza all'esaurimento delle risorse: in genere un servizio evita l'arresto anomalo dovuto all'esaurimento delle risorse, perché è difficile per le richieste in attesa fare ulteriori progressi se il servizio si è arrestato in modo anomalo. Se un servizio di backend ha molte istanze, la robustezza delle singole istanze è fondamentale per la disponibilità complessiva del servizio. Mentre un'istanza si riavvia da un arresto anomalo, altre istanze potrebbero riscontrare un carico maggiore, causando potenzialmente un errore a cascata.

Linee guida generali per i test

Durante la definizione dei tuoi scenari di test, tieni presente le seguenti linee guida.

Crea test su scala ridotta

Crea test su scala ridotta per misurare i limiti di prestazioni del server. Con una capacità server eccessiva, c'è il rischio che un test non riveli i limiti di prestazioni del servizio stesso, ma possa rivelare colli di bottiglia in altri sistemi, come gli host del client o il livello di rete.

Per ottenere risultati ottimali, considera uno scenario di test che utilizza una singola istanza di macchina virtuale (VM) o un pod Google Kubernetes Engine (GKE) per testare il servizio in modo indipendente. Per raggiungere il pieno carico sul server, se necessario, puoi utilizzare più VM, ma ricorda che possono complicare la raccolta dei dati sulle prestazioni.

Scegli pattern di carico a circuito aperto

La maggior parte dei generatori di carico utilizza il pattern a loop chiuso per limitare il numero di richieste in parallelo e ritardare le nuove richieste fino al completamento di quelle precedenti. Sconsigliamo di utilizzare questo approccio perché i client di produzione del servizio potrebbero non presentare un comportamento di limitazione di questo tipo.

Al contrario, il pattern a loop aperto consente ai generatori di carico di simulare il carico di produzione inviando le richieste a una frequenza costante, indipendentemente dalla frequenza con cui arrivano le risposte del server.

Consigliamo i seguenti generatori di carico per il test di carico del servizio di backend:

Falco notturno

Nighthawk è uno strumento open source sviluppato in coordinamento con il progetto Envoy. Puoi utilizzarlo per generare il carico client, visualizzare i benchmark e misurare le prestazioni del server per la maggior parte degli scenari di test di carico dei servizi HTTPS.

Testa HTTP/1

Per testare HTTP/1, utilizza il comando seguente:

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

Sostituisci quanto segue:

  • URI: l'URI del benchmark
  • DURATION: tempo di esecuzione del test totale in secondi
  • REQ_BODY_SIZE: dimensione del payload POST in ogni richiesta
  • CONCURRENCY: il numero totale di loop di eventi simultanei

    Questo numero deve corrispondere al numero di core della VM client

  • RPS: la frequenza target di richieste al secondo, per loop di evento

  • CONNECTIONS: il numero di connessioni simultanee, per loop di eventi

Vedi l'esempio che segue:

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

L'output di ogni esecuzione di test fornisce un istogramma delle latenze di risposta. Nell'esempio tratto dalla documentazione di Nighthawk, nota che la latenza al 99° percentile è di circa 135 microsecondi.

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

Testa HTTP/2

Per testare HTTP/2, utilizza il comando seguente:

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

Sostituisci quanto segue:

  • URI: l'URI del benchmark
  • DURATION: tempo di esecuzione del test totale in secondi
  • REQ_BODY_SIZE: dimensione del payload POST in ogni richiesta
  • CONCURRENCY: il numero totale di loop di eventi simultanei

    Questo numero deve corrispondere al numero di core della VM client

  • RPS: la frequenza target di richieste al secondo per ogni loop di eventi

  • MAX_ACTIVE_REQUESTS: numero massimo di richieste attive simultanee per ogni loop di eventi

  • MAX_CONCURRENT_STREAMS: il numero massimo di stream simultanei consentiti su ogni connessione HTTP/2

Vedi l'esempio che segue:

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 (strumento di benchmark Apache)

ab è un'alternativa meno flessibile a Nighthawk, ma è disponibile come pacchetto su quasi tutte le distribuzioni Linux. ab è consigliato solo per test semplici e veloci.

Per installare ab, utilizza il seguente comando:

  • Su Debian e Ubuntu, esegui sudo apt-get install apache2-utils.
  • Nelle distribuzioni basate su RedHat, esegui sudo yum install httpd-utils.

Dopo aver installato ab, utilizza il seguente comando per eseguirlo:

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

Sostituisci quanto segue:

  • CONCURRENCY: numero di richieste in parallelo da eseguire
  • NUM_REQUESTS: numero di richieste da eseguire
  • TIMELIMIT: numero massimo di secondi da dedicare alle richieste
  • POST_FILE: file locale contenente il payload HTTP POST
  • URI: l'URI del benchmark

Vedi l'esempio che segue:

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

Il comando nell'esempio precedente invia richieste con una contemporaneità di 200 (pattern a loop chiuso) e si arresta dopo 1.000.000 (un milione) di richieste o 600 secondi di tempo trascorso. Il comando include anche i contenuti del file body sotto forma di payload POST HTTP.

Il comando ab produce istogrammi di latenza della risposta simili a quelli di Nighthawk, ma la sua risoluzione è limitata a millisecondi, anziché microsecondi:

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)

Passaggi successivi