SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

L'analisi e la messa a punto delle prestazioni rappresentano un potente strumento per verificare la conformità delle prestazioni per i clienti.

L'analisi delle prestazioni può essere utilizzata per verificare la presenza di colli di bottiglia in un programma applicando un approccio scientifico al test degli esperimenti di ottimizzazione. Questo articolo definisce un approccio generale all'analisi e all'ottimizzazione delle prestazioni, utilizzando un server Web Go come esempio.

Go è particolarmente utile qui perché dispone di strumenti di profilazione pprof nella libreria standard.

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

strategia

Creiamo un elenco riepilogativo per la nostra analisi strutturale. Cercheremo di utilizzare alcuni dati per prendere decisioni invece di apportare modifiche basate sull'intuizione o su congetture. Per fare ciò faremo questo:

  • Determiniamo i limiti di ottimizzazione (requisiti);
  • Calcoliamo il carico delle transazioni per il sistema;
  • Eseguiamo il test (creiamo dati);
  • Osserviamo;
  • Analizziamo: tutti i requisiti sono soddisfatti?
  • Lo impostiamo scientificamente, facciamo un'ipotesi;
  • Eseguiamo un esperimento per verificare questa ipotesi.

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Architettura semplice del server HTTP

Per questo articolo utilizzeremo un piccolo server HTTP a Golang. È possibile trovare tutto il codice di questo articolo qui.

L'applicazione analizzata è un server HTTP che esegue il polling di Postgresql per ogni richiesta. Inoltre, sono presenti Prometheus, node_exporter e Grafana per la raccolta e la visualizzazione delle metriche dell'applicazione e del sistema.

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Per semplificare, consideriamo che per il dimensionamento orizzontale (e per semplificare i calcoli) ogni servizio e database vengono distribuiti insieme:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Definizione degli obiettivi

A questo punto decidiamo l’obiettivo. Cosa stiamo cercando di analizzare? Come facciamo a sapere quando è ora di finire? In questo articolo immagineremo di avere clienti e che il nostro servizio elaborerà 10 richieste al secondo.

В Libro SRE di Google I metodi di selezione e modellazione sono discussi in dettaglio. Facciamo lo stesso e costruiamo modelli:

  • Latenza: il 99% delle richieste dovrebbe essere completato in meno di 60 ms;
  • Costo: il servizio dovrebbe consumare la quantità minima di denaro che riteniamo ragionevolmente possibile. Per fare ciò, massimizziamo la produttività;
  • Pianificazione della capacità: è necessario comprendere e documentare quante istanze dell'applicazione dovranno essere eseguite, inclusa la funzionalità di scalabilità complessiva, e quante istanze saranno necessarie per soddisfare i requisiti di caricamento iniziale e provisioning ridondanza n+1.

La latenza può richiedere l'ottimizzazione oltre all'analisi, ma è chiaramente necessario analizzare il throughput. Quando si utilizza il processo SRE SLO, la richiesta di ritardo proviene dal cliente o dall'azienda, rappresentata dal proprietario del prodotto. E il nostro servizio adempirà a questo obbligo fin dall'inizio senza alcuna impostazione!

Impostazione di un ambiente di test

Con l'aiuto di un ambiente di test, saremo in grado di caricare il nostro sistema in modo misurato. A scopo di analisi verranno generati dati sulle prestazioni del servizio web.

Carico della transazione

Questo ambiente utilizza Vegetare per creare una frequenza di richieste HTTP personalizzata fino all'arresto:

$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta report

guardare

Il carico transazionale verrà applicato in fase di esecuzione. Oltre ai parametri dell'applicazione (numero di richieste, latenza di risposta) e del sistema operativo (memoria, CPU, IOPS), verrà eseguita la profilazione dell'applicazione per comprendere dove presenta problemi e come viene consumato il tempo della CPU.

Profilazione

La profilazione è un tipo di misurazione che consente di vedere dove sta andando il tempo della CPU quando un'applicazione è in esecuzione. Ti consente di determinare esattamente dove e quanto tempo viene impiegato dal processore:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Questi dati possono essere utilizzati durante l'analisi per ottenere informazioni sul tempo sprecato della CPU e sul lavoro non necessario eseguito. Go (pprof) può generare profili e visualizzarli come grafici di fiamma utilizzando un set standard di strumenti. Parlerò del loro utilizzo e della guida alla configurazione più avanti nell'articolo.

Esecuzione, osservazione, analisi.

Facciamo un esperimento. Eseguiremo, osserveremo e analizzeremo finché non saremo soddisfatti della prestazione. Scegliamo un valore di carico arbitrariamente basso da applicare per ottenere i risultati delle prime osservazioni. Ad ogni passaggio successivo aumenteremo il carico con un certo fattore di scala, scelto con qualche variazione. Ogni esecuzione del test di carico viene eseguita con il numero di richieste modificato: make load-test LOAD_TEST_RATE=X.

50 richieste al secondo

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Presta attenzione ai primi due grafici. In alto a sinistra mostra che la nostra applicazione elabora 50 richieste al secondo (pensa) e in alto a destra mostra la durata di ciascuna richiesta. Entrambi i parametri ci aiutano a guardare e analizzare se siamo entro i nostri limiti di prestazione o meno. Linea rossa sul grafico Latenza della richiesta HTTP mostra SLO a 60 ms. La linea mostra che siamo ben al di sotto del nostro tempo di risposta massimo.

Consideriamo il lato costi:

10000 richieste al secondo / 50 richieste per server = 200 server + 1

Possiamo ancora migliorare questo dato.

500 richieste al secondo

Cose più interessanti iniziano ad accadere quando il carico arriva a 500 richieste al secondo:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Ancora una volta, nel grafico in alto a sinistra puoi vedere che l'applicazione sta registrando un carico normale. Se così non fosse, c'è un problema sul server su cui è in esecuzione l'applicazione. Il grafico della latenza della risposta si trova in alto a destra e mostra che 500 richieste al secondo hanno comportato un ritardo nella risposta di 25-40 ms. Il 99° percentile si adatta ancora bene allo SLO di 60 ms scelto sopra.

In termini di costo:

10000 richieste al secondo / 500 richieste per server = 20 server + 1

Tutto può ancora essere migliorato.

1000 richieste al secondo

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Ottimo lancio! L'applicazione mostra che ha elaborato 1000 richieste al secondo, ma il limite di latenza è stato violato dallo SLO. Questo può essere visto nella riga p99 nel grafico in alto a destra. Nonostante la linea p100 sia molto più alta, i ritardi effettivi sono superiori al massimo di 60ms. Immergiamoci nella profilazione per scoprire cosa fa effettivamente l'applicazione.

Profilazione

Per la profilazione, impostiamo il carico su 1000 richieste al secondo, quindi utilizziamo pprof per acquisire dati per scoprire dove l'applicazione sta impiegando il tempo della CPU. Questo può essere fatto attivando l'endpoint HTTP pprof, quindi, sotto carico, salva i risultati utilizzando curl:

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

I risultati possono essere visualizzati in questo modo:

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Il grafico mostra dove e quanto l'applicazione impiega il tempo della CPU. Dalla descrizione di Brendan Gregg:

L'asse X è la popolazione del profilo dello stack, in ordine alfabetico (non è il tempo), l'asse Y mostra la profondità dello stack, contando da zero in [in alto]. Ogni rettangolo è uno stack frame. Più ampia è la cornice, più spesso è presente negli stack. Ciò che è in alto viene eseguito sulla CPU e ciò che è sotto sono gli elementi figlio. I colori di solito non significano nulla, ma sono semplicemente scelti in modo casuale per differenziare i fotogrammi.

Analisi - ipotesi

Per l'ottimizzazione, ci concentreremo sulla ricerca del tempo sprecato della CPU. Cercheremo le maggiori fonti di spesa inutile e le rimuoveremo. Ebbene, dato che la profilazione rivela in modo molto accurato dove esattamente l'applicazione sta impiegando il tempo del processore, potrebbe essere necessario ripeterla più volte e sarà anche necessario modificare il codice sorgente dell'applicazione, eseguire nuovamente i test e verificare che le prestazioni si avvicinino all'obiettivo.

Seguendo i consigli di Brendan Gregg, leggeremo il grafico dall'alto verso il basso. Ogni riga visualizza uno stack frame (chiamata di funzione). La prima riga è il punto di ingresso nel programma, il genitore di tutte le altre chiamate (in altre parole, tutte le altre chiamate la avranno nel proprio stack). La riga successiva è già diversa:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Se passi il cursore sul nome di una funzione nel grafico, verrà visualizzato il tempo totale in cui è rimasta nello stack durante il debug. La funzione HTTPServe era presente il 65% delle volte, altre funzioni di runtime runtime.mcall, mstart и gc, ha occupato il resto del tempo. Fatto curioso: il 5% del tempo totale viene dedicato alle query DNS:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Gli indirizzi che il programma cerca appartengono a Postgresql. Clicca su FindByAge:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

È interessante notare che il programma mostra che, in linea di principio, ci sono tre fonti principali che aggiungono ritardi: apertura e chiusura delle connessioni, richiesta di dati e connessione al database. Il grafico mostra che le richieste DNS, l'apertura e la chiusura delle connessioni occupano circa il 13% del tempo totale di esecuzione.

Ipotesi: Il riutilizzo delle connessioni tramite il pooling dovrebbe ridurre il tempo di una singola richiesta HTTP, consentendo una velocità effettiva più elevata e una latenza inferiore.

Configurazione dell'applicazione: esperimento

Aggiorniamo il codice sorgente, proviamo a rimuovere la connessione a Postgresql per ogni richiesta. La prima opzione è usare pool di connessione a livello di applicazione. In questo esperimento noi configuriamolo pool di connessioni utilizzando il driver SQL per Go:

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

Esecuzione, osservazione, analisi

Dopo aver riavviato il test con 1000 richieste al secondo, è chiaro che i livelli di latenza di p99 sono tornati alla normalità con uno SLO di 60ms!

Qual è il costo?

10000 richieste al secondo / 1000 richieste per server = 10 server + 1

Facciamolo ancora meglio!

2000 richieste al secondo

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Raddoppiando il carico si vede la stessa cosa, il grafico in alto a sinistra mostra che l'applicazione riesce a processare 2000 richieste al secondo, p100 è inferiore a 60ms, p99 soddisfa lo SLO.

In termini di costo:

10000 richieste al secondo / 2000 richieste per server = 5 server + 1

3000 richieste al secondo

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Qui l'applicazione può elaborare 3000 richieste con una latenza p99 inferiore a 60 ms. Lo SLO non viene violato e il costo viene accettato come segue:

10000 richieste al secondo / ogni 3000 richieste per server = 4 server + 1 (l'autore ha arrotondato, ca. traduttore)

Proviamo un altro giro di analisi.

Analisi - ipotesi

Raccogliamo e visualizziamo i risultati del debug dell'applicazione a 3000 richieste al secondo:

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Ancora il 6% del tempo viene dedicato alla creazione di connessioni. La configurazione del pool ha migliorato le prestazioni, ma puoi comunque vedere che l'applicazione continua a lavorare sulla creazione di nuove connessioni al database.

Ipotesi: Le connessioni, nonostante la presenza di un pool, vengono comunque interrotte e ripulite, quindi l'applicazione deve reimpostarle. L'impostazione del numero di connessioni in sospeso sulla dimensione del pool dovrebbe aiutare con la latenza riducendo al minimo il tempo impiegato dall'applicazione per creare una connessione.

Configurazione dell'applicazione: esperimento

Tentativo di installazione MaxIdleConns uguale alla dimensione della piscina (descritto anche qui):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

Esecuzione, osservazione, analisi

3000 richieste al secondo

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

p99 è inferiore a 60 ms con p100 significativamente inferiore!

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

Controllando il grafico della fiamma si vede che il collegamento non si nota più! Controlliamo più in dettaglio pg(*conn).query – inoltre non notiamo la connessione stabilita qui.

SRE: Analisi delle prestazioni. Metodo di configurazione utilizzando un semplice server Web in Go

conclusione

L'analisi delle prestazioni è fondamentale per comprendere se le aspettative dei clienti e i requisiti non funzionali vengono soddisfatti. L'analisi confrontando le osservazioni con le aspettative dei clienti può aiutare a determinare cosa è accettabile e cosa non lo è. Go fornisce potenti strumenti integrati nella libreria standard che rendono l'analisi semplice e accessibile.

Fonte: habr.com

Aggiungi un commento