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

Durante l'integrazione di un servizio di backend con il bilanciatore del carico delle applicazioni, è importante misurare il rendimento di un servizio di backend da solo, in assenza di un bilanciatore del carico. I test di carico in condizioni controllate ti aiutano a valutare i compromessi della pianificazione della capacità tra diverse dimensioni del rendimento, come throughput e latenza. Poiché una pianificazione attenta 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 è interessata quando il sistema è sovracaricato.

Obiettivi dei test di carico

Un test di carico tipico misura il comportamento visibile all'esterno del servizio di backend in diverse dimensioni di carico. Di seguito sono riportate alcune delle dimensioni più pertinenti di questi test:

  • Throughput delle richieste:il numero di richieste gestite al secondo.
  • Contemporaneità delle richieste:il numero di richieste elaborate contemporaneamente.
  • Throughput della connessione: il numero di connessioni avviate dai client al secondo. La maggior parte dei servizi che utilizzano Transport Layer Security (TLS) presenta un certo ovvio overhead di trasporto di rete e negoziazione TLS associato a ogni connessione indipendente dall'elaborazione delle richieste.
  • Contemporaneità delle 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.

  • Tasso di errori: la frequenza con cui le richieste causano errori, ad esempio errori HTTP 5xx e chiusure premature delle connessioni.

Per valutare lo stato del server sotto carico, una procedura di test di carico potrebbe anche raccogliere le seguenti metriche del servizio interno:

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

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

  • Utilizzo di altre risorse limitate:risorse non di sistema che potrebbero essere esaurite sotto carico, ad esempio a livello di applicazione.

    Ecco alcuni esempi di risorse di questo tipo:

    • Un pool delimitato di thread o processi worker.
    • Per un server applicazioni che utilizza thread, è comune limitare il numero di thread worker in esecuzione contemporaneamente. I limiti di dimensione del pool di thread sono utili per evitare l'esaurimento della memoria e della CPU, ma le impostazioni predefinite sono spesso molto conservative. Limiti troppo bassi potrebbero impedire un utilizzo adeguato delle risorse di sistema.
    • Alcuni server utilizzano pool di processi anziché pool di thread. Ad esempio, un server Apache configurato con il modello di pre-elaborazione multi-tasking, assegna un processo a ogni connessione client. Pertanto, il limite di dimensione del pool determina il limite superiore alla concorrenza delle connessioni.
    • Un servizio di cui è stato eseguito il deployment come frontend di un altro servizio che ha un pool di connessioni di backend di dimensioni limitate.

Pianificazione della capacità rispetto ai test di sovraccarico

Gli strumenti di test di carico ti aiutano a misurare singolarmente le diverse dimensioni di scalabilità. Per la pianificazione della capacità, determina la soglia di carico per il rendimento accettabile in più dimensioni. Ad esempio, anziché misurare il limite assoluto di una richiesta di servizio, valuta la possibilità di misurare quanto segue:

  • La tasso di richieste con cui il servizio può essere pubblicato con una latenza del 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 di utilizzo delle risorse di sistema. Tieni presente che l'utilizzo ottimale varia in base all'applicazione e potrebbe essere notevolmente inferiore al 100%. Ad esempio, con un picco di utilizzo della memoria all'80%, l'applicazione potrebbe essere in grado di gestire picchi di carico minori meglio che se l'utilizzo di picco fosse al 99%.

Sebbene sia importante utilizzare i risultati dei test di carico per prendere decisioni relative alla pianificazione della capacità, è altrettanto importante capire il comportamento di un servizio quando il carico supera la capacità. Di seguito sono riportati alcuni comportamenti del server spesso valutati utilizzando i test di sovraccarico:

  • Ridondanza: quando un servizio riceve richieste o connessioni in entrata eccessive, potrebbe rispondere rallentando tutte le richieste o rifiutandone alcune per mantenere un rendimento accettabile per le rimanenti. Consigliamo di adottare quest'ultimo approccio per evitare i timeout del client prima di ricevere una risposta e per ridurre il rischio di esaurimento della memoria diminuendo la concorrenza delle richieste sul server.

  • Resilienza all'esaurimento delle risorse: in genere un servizio evita di bloccarsi per esaurimento delle risorse perché è difficile per le richieste in attesa fare ulteriori progressi se il servizio si è bloccato. Se un servizio di backend ha molte istanze, la robustezza delle singole istanze è fondamentale per la disponibilità complessiva del servizio. Quando un'istanza si riavvia dopo un arresto anomalo, altre istanze potrebbero subire un carico maggiore, causando potenzialmente un errore a cascata.

Linee guida generali per i test

Quando definisci i casi di test, tieni presenti le seguenti linee guida.

Creare test su piccola scala

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

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

Scegliere i pattern di carico a ciclo aperto

La maggior parte dei generatori di carico utilizza il pattern a ciclo chiuso per limitare il numero di richieste simultanee e ritardare le nuove richieste fino al completamento di quelle precedenti. Sconsigliamo questo approccio perché i client di produzione del servizio potrebbero non mostrare questo comportamento di throttling.

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

Per i test di carico del servizio di backend, consigliamo i seguenti generatori di carico:

Nighthawk

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

Test HTTP/1

Per testare HTTP/1, utilizza il seguente 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

Sostituisci quanto segue:

  • URI: l'URI da sottoporre a benchmark
  • DURATION: tempo di esecuzione totale del test in secondi
  • REQ_BODY_SIZE: dimensioni del payload POST in ogni richiesta
  • CONCURRENCY: il numero totale di loop di eventi concorrenziali

    Questo numero deve corrispondere al numero di core della VM client

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

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

Vedi il seguente esempio:

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 del test fornisce un'istogramma delle latenze di risposta. Nell'esempio della documentazione di Nighthawk, tieni presente che la latenza del 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

Testare HTTP/2

Per testare HTTP/2, utilizza il seguente 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

Sostituisci quanto segue:

  • URI: l'URI da sottoporre a benchmark
  • DURATION: tempo di esecuzione totale del test in secondi
  • REQ_BODY_SIZE: dimensioni del payload POST in ogni richiesta
  • CONCURRENCY: il numero totale di loop di eventi concorrenziali

    Questo numero deve corrispondere al numero di core della VM client

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

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

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

Vedi il seguente esempio:

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 rapidi e semplici.

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 simultanee 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 del POST HTTP
  • URI: l'URI da sottoporre a benchmark

Vedi il seguente esempio:

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

Il comando nell'esempio precedente invia richieste con una concorrenza di 200 (pattern a ciclo chiuso) e si interrompe dopo 1.000.000 (un milione) di richieste o 600 secondi di tempo trascorso. Il comando include anche i contenuti del file body come payload POST HTTP.

Il comando ab genera istogrammi della latenza di risposta simili a quelli di Nighthawk, ma la sua risoluzione è limitata ai millisecondi anziché ai 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