Un'API che scorre senza problemi in staging può crollare nel momento in cui arriva il traffico reale. Il fallimento è raramente drammatico. È una morte per mille tagli. Un thread bloccato qui. Una query ridondante lì. Sotto un carico leggero, queste piccole inefficienze rimangono nascoste. Sotto la pressione della produzione, si accumulano in starvation del thread pool, soffocamento del database e tempi di risposta che distruggono la fiducia dell'utente. Il tuning delle prestazioni non consiste nel trovare un'unica soluzione magica. Consiste nel costruire un sistema in cui ogni livello rispetti la scarsità di thread, memoria e connessioni al database. Quando tratti queste risorse come finite, smetti di reagire ai disservizi e inizi a prevenirli.

Elimina lo sync-over-async prima che sia la fine per te

Il pattern più distruttivo in assoluto nelle applicazioni ASP.NET Core ad alto traffico è lo sync-over-async. Lo vedi quando qualcuno chiama .Result o .Wait() su un metodo asincrono perché ha bisogno del valore immediatamente e non vuole rifattorizzare lo stack di chiamate. Quella decisione blocca il thread chiamante. Il thread rimane inattivo, in attesa di un lavoro che si sta già svolgendo altrove, ma il runtime non può riutilizzarlo per un'altra richiesta.

Quando un numero sufficiente di richieste lo fa, il thread pool va in starvation. Il grafico della CPU sembra sano perché i processori non sono occupati, eppure la latenza esplode. Le richieste si mettono in coda, aspettando thread che non si libereranno mai. La soluzione è meccanica ma richiede disciplina: usa await lungo tutto lo stack di chiamate. Se un metodo chiama un'API async, deve essere esso stesso async. Non ci sono scorciatoie. Ogni blocco sincrono che rimuovi recupera spazio prezioso per il traffico reale.

Smetti di sprecare risorse su client disconnessi

I client si disconnettono. I browser chiudono le schede. Le app mobile perdono il segnale. Se il server non sa che il client se n'è andato, continua a eseguire query al database, analizzare JSON e consumare thread per una risposta che nessuno riceverà. Passa un CancellationToken a ogni operazione async che lo supporti. Ciò significa query di Entity Framework, chiamate HTTP con HttpClient e qualsiasi lavoro in background a lunga esecuzione.

Quando la connessione cade, il token attiva la cancellazione e il lavoro si interrompe immediatamente. Questo preserva i cicli di CPU del database e restituisce i thread al pool più velocemente. È un piccolo cambiamento nelle firme dei metodi che ripaga ampiamente sotto carico.

Metti in cache ciò che non cambia

Il tuo catalogo prodotti probabilmente non cambia tra una richiesta e l'altra. I tuoi flag di configurazione certamente no. Eppure molte API interrogano ripetutamente il database per gli stessi dati statici. L'output caching in ASP.NET Core ti permette di memorizzare le risposte renderizzate e servirle direttamente dalla memoria senza dover toccare nuovamente i controller o il database.

Usa l'invalidazione basata su tag con attenzione. Quando aggiorni un prodotto, invalida solo il tag associato a quella categoria o a quell'articolo. Non è necessario svuotare l'intera cache. Questo mantiene alto il tuo cache hit ratio e basso il numero di query al database.

Ottimizza prima l'accesso ai dati

Le chiamate al database consumano la maggior parte del tempo di richiesta nella maggior parte delle API. Prima di ottimizzare qualsiasi altra cosa, guarda qui.

Se stai interrogando i dati solo per visualizzarli e non hai intenzione di aggiornarli, aggiungi AsNoTracking() alle tue query Entity Framework. EF Core salta il change tracking e la creazione di snapshot