A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Come in la maggior parte dei post, c'è un problema con un servizio distribuito, chiameremo questo servizio Alvin. Questa volta non ho scoperto io stesso il problema, mi hanno informato i ragazzi del lato cliente.

Un giorno mi sono svegliato con un'e-mail scontenta a causa dei lunghi ritardi con Alvin, che avevamo pianificato di lanciare nel prossimo futuro. Nello specifico, il client ha riscontrato una latenza del 99° percentile nell'ordine di 50 ms, ben al di sopra del nostro budget di latenza. Ciò è stato sorprendente poiché ho testato ampiamente il servizio, soprattutto sulla latenza, che è una lamentela comune.

Prima di mettere Alvin in fase di test, ho eseguito molti esperimenti con 40 query al secondo (QPS), mostrando tutti una latenza inferiore a 10 ms. Ero pronto a dichiarare che non ero d'accordo con i loro risultati. Ma dando un'altra occhiata alla lettera, ho notato qualcosa di nuovo: non avevo testato esattamente le condizioni menzionate, il loro QPS era molto inferiore al mio. Ho testato a 40k QPS, ma solo a 1k. Ho eseguito un altro esperimento, questa volta con un QPS inferiore, solo per accontentarli.

Dato che ne parlo nel blog, probabilmente hai già capito che i loro numeri erano giusti. Ho testato più e più volte il mio client virtuale, con lo stesso risultato: un numero basso di richieste non solo aumenta la latenza, ma aumenta il numero di richieste con una latenza superiore a 10 ms. In altre parole, se a 40k QPS circa 50 richieste al secondo superavano i 50 ms, allora a 1k QPS c’erano 100 richieste superiori a 50 ms al secondo. Paradosso!

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Restringere la ricerca

Di fronte a un problema di latenza in un sistema distribuito con molti componenti, il primo passo è creare un breve elenco di sospetti. Esaminiamo un po' più a fondo l'architettura di Alvin:

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Un buon punto di partenza è un elenco delle transizioni I/O completate (chiamate di rete/ricerche su disco, ecc.). Proviamo a capire dov'è il ritardo. Oltre all'ovvio I/O con il client, Alvin fa un passo in più: accede all'archivio dati. Tuttavia, questo storage opera nello stesso cluster di Alvin, quindi la latenza dovrebbe essere inferiore a quella del client. Dunque, l'elenco degli indagati:

  1. Chiamata di rete dal cliente ad Alvin.
  2. Chiamata di rete da Alvin all'archivio dati.
  3. Cerca su disco nell'archivio dati.
  4. Chiamata di rete dal data warehouse ad Alvin.
  5. Chiamata di rete da Alvin a un cliente.

Proviamo a cancellare alcuni punti.

L'archiviazione dei dati non ha nulla a che fare con questo

La prima cosa che ho fatto è stata convertire Alvin in un server ping-ping che non elabora le richieste. Quando riceve una richiesta, restituisce una risposta vuota. Se la latenza diminuisce, non è inaudito un bug nell'implementazione di Alvin o del data warehouse. Nel primo esperimento otteniamo il seguente grafico:

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Come puoi vedere, non ci sono miglioramenti quando si utilizza il server ping-ping. Ciò significa che il data warehouse non aumenta la latenza e la lista dei sospetti viene dimezzata:

  1. Chiamata di rete dal cliente ad Alvin.
  2. Chiamata di rete da Alvin a un cliente.

Grande! L'elenco si sta restringendo rapidamente. Pensavo di aver quasi capito il motivo.

gRPC

Ora è il momento di presentarti un nuovo giocatore: gRPC. Questa è una libreria open source di Google per la comunicazione in-process RPC. Sebbene gRPC ben ottimizzato e ampiamente utilizzato, era la prima volta che lo utilizzavo su un sistema di queste dimensioni e mi aspettavo che la mia implementazione non fosse ottimale, per usare un eufemismo.

disponibilità gRPC nello stack ha dato origine a una nuova domanda: forse è la mia implementazione o me stesso gRPC causando problemi di latenza? Aggiunta di un nuovo sospetto all'elenco:

  1. Il cliente chiama la biblioteca gRPC
  2. Biblioteca gRPC effettua una chiamata di rete alla libreria sul client gRPC sul server
  3. Biblioteca gRPC contatta Alvin (nessuna operazione in caso di server ping-pong)

Per darti un'idea di come appare il codice, la mia implementazione client/Alvin non è molto diversa da quelle client-server esempi asincroni.

Nota: l'elenco sopra è un po' semplificato perché gRPC rende possibile utilizzare il proprio modello di threading (modello?), in cui lo stack di esecuzione è intrecciato gRPC e implementazione da parte dell'utente. Per semplicità ci atterremo a questo modello.

La profilazione risolverà tutto

Dopo aver cancellato gli archivi dati, pensavo di aver quasi finito: “Adesso è facile! Applichiamo il profilo e scopriamo dove si verifica il ritardo." IO grande appassionato di profilatura di precisione, perché le CPU sono molto veloci e molto spesso non rappresentano il collo di bottiglia. La maggior parte dei ritardi si verifica quando il processore deve interrompere l'elaborazione per fare qualcos'altro. La profilazione accurata della CPU fa proprio questo: registra accuratamente tutto cambi di contesto e chiarisce dove si verificano i ritardi.

Ho preso quattro profili: con QPS alto (bassa latenza) e con server ping-pong con QPS basso (alta latenza), sia lato client che lato server. E per ogni evenienza, ho anche preso un profilo di esempio del processore. Quando confronto i profili, di solito cerco uno stack di chiamate anomalo. Ad esempio, l'aspetto negativo con un'elevata latenza è che ci sono molti più cambi di contesto (10 volte o più). Ma nel mio caso, il numero di cambi di contesto era quasi lo stesso. Con mio orrore, non c'era nulla di significativo lì.

Debug aggiuntivo

Ero disperato. Non sapevo quali altri strumenti avrei potuto utilizzare e il mio piano successivo era essenzialmente quello di ripetere gli esperimenti con diverse varianti piuttosto che diagnosticare chiaramente il problema.

Cosa succede se

Fin dall'inizio ero preoccupato per la latenza specifica di 50 ms. Questo è un momento molto importante. Ho deciso di tagliare parti del codice finché non fossi riuscito a capire esattamente quale parte stava causando questo errore. Poi è arrivato un esperimento che ha funzionato.

Come al solito, col senno di poi sembra che tutto fosse ovvio. Ho posizionato il client sulla stessa macchina di Alvin e ho inviato una richiesta a localhost. E l'aumento della latenza è sparito!

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Qualcosa non andava con la rete.

Apprendimento delle competenze di ingegnere di rete

Devo ammetterlo: la mia conoscenza delle tecnologie di rete è pessima, soprattutto considerando il fatto che ci lavoro ogni giorno. Ma la rete era il principale sospettato e dovevo imparare come eseguirne il debug.

Fortunatamente, Internet ama coloro che vogliono imparare. La combinazione di ping e tracert sembrava un buon inizio per il debug dei problemi di trasporto di rete.

Per prima cosa ho lanciato Psping alla porta TCP di Alvin. Ho usato le impostazioni predefinite: niente di speciale. Su più di mille ping, nessuno ha superato i 10 ms, ad eccezione del primo di riscaldamento. Ciò è contrario all'aumento della latenza di 50 ms osservato al 99° percentile: lì per ogni 100 richieste dovremmo vedere circa una richiesta con una latenza di 50 ms.

Poi ho provato tracert: Potrebbe esserci un problema in uno dei nodi lungo il percorso tra Alvin e il client. Ma anche il tracciante è tornato a mani vuote.

Quindi non è stato il mio codice, l'implementazione gRPC o la rete a causare il ritardo. Cominciavo a temere che non l'avrei mai capito.

Ora, su quale sistema operativo siamo?

gRPC ampiamente utilizzato su Linux, ma esotico su Windows. Ho deciso di provare un esperimento, che ha funzionato: ho creato una macchina virtuale Linux, ho compilato Alvin per Linux e l'ho distribuito.

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

Ed ecco cosa è successo: il server ping-pong Linux non ha avuto gli stessi ritardi di un host Windows simile, sebbene la fonte dei dati non fosse diversa. Si scopre che il problema risiede nell'implementazione gRPC per Windows.

Algoritmo di Nagle

Per tutto questo tempo ho pensato che mi mancasse una bandiera gRPC. Adesso capisco di cosa si tratta veramente gRPC Manca la bandiera di Windows. Ho trovato una libreria RPC interna che ero sicuro avrebbe funzionato bene per tutti i flag impostati Winsock. Quindi ho aggiunto tutti questi flag a gRPC e ho distribuito Alvin su Windows, in un server ping-pong Windows con patch!

A volte più è meno. Quando la riduzione del carico comporta un aumento della latenza

quasi Fatto: ho iniziato a rimuovere i flag aggiunti uno alla volta fino al ritorno della regressione in modo da poter individuare la causa. Era famigerato TCP_NODELAY, Cambio dell'algoritmo di Nagle.

Algoritmo di Nagle tenta di ridurre il numero di pacchetti inviati su una rete ritardando la trasmissione dei messaggi finché la dimensione del pacchetto non supera un certo numero di byte. Sebbene ciò possa essere utile per l'utente medio, è distruttivo per i server in tempo reale poiché il sistema operativo ritarderà alcuni messaggi, causando ritardi su QPS bassi. U gRPC questo flag è stato impostato nell'implementazione Linux per i socket TCP, ma non in Windows. Io sono questo corretto.

conclusione

La latenza più elevata con un QPS basso è stata causata dall'ottimizzazione del sistema operativo. In retrospettiva, la profilazione non ha rilevato la latenza perché è stata eseguita in modalità kernel anziché in modalità utente. Non so se l'algoritmo di Nagle possa essere osservato attraverso le acquisizioni ETW, ma sarebbe interessante.

Per quanto riguarda l'esperimento localhost, probabilmente non ha toccato il codice di rete effettivo e l'algoritmo di Nagle non è stato eseguito, quindi i problemi di latenza sono scomparsi quando il client ha raggiunto Alvin tramite localhost.

La prossima volta che vedrai un aumento della latenza man mano che il numero di richieste al secondo diminuisce, l'algoritmo di Nagle dovrebbe essere nella tua lista di sospetti!

Fonte: habr.com

Aggiungi un commento