Perché otteniamo un picco improvviso nei tempi di risposta?


12

Abbiamo un'API implementata utilizzando ServiceStack che è ospitato in IIS. Durante l'esecuzione del test di carico dell'API abbiamo scoperto che i tempi di risposta sono buoni ma che si deteriorano rapidamente non appena raggiungiamo circa 3.500 utenti simultanei per server. Abbiamo due server e quando li colpiamo con 7000 utenti i tempi di risposta medi si situano sotto i 500 ms per tutti gli endpoint. Le scatole sono dietro un bilanciamento del carico in modo da ottenere 3.500 concorrenti per server. Tuttavia, non appena aumentiamo il numero di utenti simultanei totali, vediamo un aumento significativo dei tempi di risposta. Aumentare gli utenti simultanei a 5.000 per server ci dà un tempo di risposta medio per endpoint di circa 7 secondi.

La memoria e la CPU sui server sono piuttosto basse, sia quando i tempi di risposta sono buoni sia quando si deteriorano. Al picco con 10.000 utenti simultanei, la CPU è in media appena inferiore al 50% e la RAM si attesta su 3-4 GB su 16. Questo ci lascia pensare che stiamo raggiungendo un qualche limite. Lo screenshot seguente mostra alcuni contatori chiave in perfmon durante un test di carico con un totale di 10.000 utenti simultanei. Il contatore evidenziato è richieste / secondo. A destra dello screenshot puoi vedere il grafico delle richieste al secondo diventare davvero irregolari. Questo è l'indicatore principale per i tempi di risposta lenti. Non appena vediamo questo schema notiamo tempi di risposta lenti nel test di carico.

screenshot di perfmon con richieste al secondo evidenziate

Come possiamo risolvere questo problema di prestazioni? Stiamo cercando di identificare se si tratta di un problema di codifica o di configurazione. Esistono impostazioni in web.config o IIS che potrebbero spiegare questo comportamento? Il pool di applicazioni esegue .NET v4.0 e la versione IIS è 7.5. L'unica modifica apportata dalle impostazioni predefinite è aggiornare il valore Lunghezza coda pool di applicazioni da 1.000 a 5.000. Abbiamo anche aggiunto le seguenti impostazioni di configurazione al file Aspnet.config:

<system.web>
    <applicationPool 
        maxConcurrentRequestsPerCPU="5000"
        maxConcurrentThreadsPerCPU="0" 
        requestQueueLimit="5000" />
</system.web>

Più dettagli:

Lo scopo dell'API è quello di combinare i dati provenienti da varie fonti esterne e restituirli come JSON. Attualmente utilizza un'implementazione della cache InMemory per memorizzare nella cache singole chiamate esterne a livello di dati. La prima richiesta a una risorsa recupererà tutti i dati richiesti e qualsiasi richiesta successiva per la stessa risorsa otterrà risultati dalla cache. Abbiamo un "runner cache" che viene implementato come processo in background che aggiorna le informazioni nella cache a determinati intervalli prestabiliti. Abbiamo aggiunto il blocco attorno al codice che recupera i dati dalle risorse esterne. Abbiamo anche implementato i servizi per recuperare i dati dalle fonti esterne in modo asincrono in modo tale che l'endpoint dovrebbe essere lento solo come la chiamata esterna più lenta (a meno che ovviamente non abbiamo dati nella cache). Questo viene fatto usando la classe System.Threading.Tasks.Task.Potremmo essere colpiti da una limitazione in termini di numero di thread disponibili per il processo?


5
Quanti core ha la tua CPU? Forse stai esaurendo un core. Quando il numero magico è del 50%, 25% o 12,5%, ciò suggerisce che hai raggiunto il limite massimo di un nucleo e per qualche motivo non sei in grado di utilizzare gli altri nuclei inattivi. Verificare la presenza di un nucleo massimo.
David Schwartz,

1
Hai un thread per richiesta? Quindi per 5000 richieste hai 5000 thread? Se lo fai, questo è probabilmente il tuo problema. Dovresti invece creare un pool di thread e utilizzare il pool di thread per elaborare le richieste, accodando le richieste quando arrivano al pool di thread. Quando un thread ha terminato una richiesta, può elaborare una richiesta fuori dalla coda. Questo tipo di discussione è la migliore per StackOverflow. Troppi thread significa troppi cambi di contesto.
Matt,

1
Solo un controllo di integrità qui, hai provato a disattivare tutti i tuoi processi in background e vedi qual è il comportamento solo per il JSON che restituisce dati statici dalla cache? In altre parole, rendere JSON richiede dati statici e rimuovere le "chiamate asincrone esterne" che aggiornano completamente la cache. Inoltre, a seconda della quantità di dati JSON offerti su ogni richiesta, hai pensato al throughput della tua rete e se le richieste stanno iniziando a eseguire il backup perché i server non riescono a inviare i dati abbastanza velocemente?
Robert,

1
+1 al suggerimento di Davids sopra. Dovresti davvero ripetere il test e guardare attentamente ogni utilizzo del core. Ti suggerirei di farlo al più presto per eliminarlo se non altro. In secondo luogo, sono un po 'sospettoso della tua cache. La contesa tra blocchi può mostrare esattamente questo tipo di comportamento: in alcuni punti critici i blocchi causano ritardi che a loro volta mantengono i blocchi più a lungo del normale, causando un punto di ribaltamento in cui le cose scendono rapidamente. Puoi condividere il tuo codice di cache e blocco?
Steve Cook,

1
Qual è la configurazione del disco per i server (supponendo che, poiché sono bilanciati dal carico, la configurazione del disco è la stessa)? Puoi pubblicare tutte le specifiche per le unità / i server nel tuo post iniziale? Hai lanciato un perfmon sul disco (i) sull'unità fisica su cui sono presenti IIS E i file di registro di IIS? È possibile che si verifichino problemi con il disco in quanto sono richieste 3.500 richieste = 3.500+ log IIS. Se si trovano sullo stesso disco / partizione, potresti avere un grosso problema lì.
Techie Joe,

Risposte:


2

A seguito di @DavidSchwartz e @Matt questo sembra un thread, blocca la gestione dei problemi.

Suggerisco:

  1. Congelare le chiamate esterne e la cache generata per esse ed eseguire il test di carico con informazioni esterne statiche solo per eliminare qualsiasi problema non correlato al lato server - ambiente.

  2. Utilizzare i pool di thread se non li si utilizza.

  3. A proposito di chiamate esterne che hai detto "Abbiamo anche implementato i servizi per recuperare i dati dalle fonti esterne in modo asincrono in modo tale che l'endpoint dovrebbe essere solo lento come la chiamata esterna più lenta (a meno che non abbiamo ovviamente dei dati nella cache). "

Le domande sono: - Hai controllato se alcuni dati della cache sono bloccati durante la chiamata esterna o solo quando hai scritto il risultato della chiamata esterna nella cache? (troppo ovvio ma devo dire). - Bloccate l'intera cache o parti di essa? (troppo ovvio ma devo dire). - Anche se sono asincroni, con quale frequenza vengono eseguite le chiamate esterne? Anche se non vengono eseguiti così spesso, potrebbero essere bloccati da una quantità eccessiva di richieste alla cache dalle chiamate dell'utente mentre la cache è bloccata. Questo scenario di solito mostra una percentuale fissa di CPU utilizzata perché molti thread sono in attesa a intervalli fissi e anche il "blocco" deve essere gestito. - Hai verificato se le attività esterne significano che anche il tempo di risposta aumenta quando arriva lo scenario lento?

Se il problema persiste, suggerirei di evitare la classe Task e di effettuare chiamate esterne attraverso lo stesso pool di thread che gestisce le richieste dell'utente. Questo per evitare lo scenario precedente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.