Come in
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!
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:
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:
- Chiamata di rete dal cliente ad Alvin.
- Chiamata di rete da Alvin all'archivio dati.
- Cerca su disco nell'archivio dati.
- Chiamata di rete dal data warehouse ad Alvin.
- 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:
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:
- Chiamata di rete dal cliente ad Alvin.
- 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
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:
- Il cliente chiama la biblioteca
gRPC
- Biblioteca
gRPC
effettua una chiamata di rete alla libreria sul clientgRPC
sul server - 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
Nota: l'elenco sopra è un po' semplificato perché
gRPC
rende possibile utilizzare il proprio modello di threading (modello?), in cui lo stack di esecuzione è intrecciatogRPC
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
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!
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
Poi ho provato
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.
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
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
gRPC
questo flag è stato impostato nell'implementazione Linux per i socket TCP, ma non in Windows. Io sono questo
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
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