Introduzione alle prestazioni
Le prestazioni del database sono un argomento vasto e complesso, che si estende su un intero stack di componenti: il database, la rete, il driver di database e i livelli di accesso ai dati, ad esempio EF Core. Anche se livelli generali e O/RMs come EF Core semplificano notevolmente lo sviluppo di applicazioni e migliorano la gestibilità delle applicazioni, a volte possono essere opachi, nascondendo dettagli interni critici per le prestazioni, ad esempio l'esecuzione di SQL. Questa sezione prova a fornire una panoramica dei modi per ottenere buone prestazioni con EF Core e per evitare problemi comuni che possono compromettere le prestazioni dell'applicazione.
Identificare colli di bottiglia e misurare, misurare, misurare, misurare
Come sempre con le prestazioni, è importante non affrettarsi a ottimizzare senza dati che mostrano un problema. Come disse il grande Donald Knuth, "L'ottimizzazione prematura è la radice di tutti i mali". La sezione relativa alla diagnosi delle prestazioni illustra diversi modi per capire dove l'applicazione sta dedicando tempo alla logica del database e come individuare aree problematiche specifiche. Dopo aver identificato una query lenta, è possibile prendere in considerazioni le soluzioni: manca un indice nel database? È consigliabile provare altri modelli di query?
Definire sempre il benchmark del codice e le possibili alternative: la sezione relativa alla diagnosi delle prestazioni contiene un benchmark di esempio con BenchmarkDotNet, che è possibile usare come modello per i propri benchmark. Non presupporre che i benchmark pubblici generali siano applicabili così come sono al caso d'uso specifico. Un'ampia gamma di fattori, ad esempio latenza del database, complessità delle query e quantità effettive di dati nelle tabelle, può avere un effetto profondo sull'individuazione della soluzione migliore. Ad esempio, molti benchmark pubblici vengono eseguiti in condizioni di rete ideali, in cui la latenza per il database è quasi zero e con query estremamente leggere che richiedono un'elaborazione (o I/O del disco) minima sul lato database. Sebbene siano utili per confrontare i sovraccarichi di runtime di diversi livelli di accesso ai dati, le differenze risultano in genere trascurabili in un'applicazione reale, in cui il database esegue il lavoro effettivo e la latenza per il database è un fattore significativo delle prestazioni.
Aspetti delle prestazioni di accesso ai dati
Le prestazioni generali di accesso ai dati possono essere suddivise nelle categorie generali seguenti:
- Prestazioni pure del database. Con il database relazionale, EF converte le query LINQ dell'applicazione nelle istruzioni SQL eseguite dal database. Queste istruzioni SQL possono essere eseguite in modo più o meno efficiente. L'indice corretto nella posizione corretta può fare la differenza per le prestazioni di SQL o la riscrittura della query LINQ può consentire a EF di generare una query SQL migliore.
- Trasferimento dati di rete. Analogamente a qualsiasi sistema di rete, è importante limitare la quantità di dati in transito. È quindi necessario assicurarsi di inviare e caricare solo i dati che saranno effettivamente necessari, ma anche di evitare l'effetto definito "esplosione cartesiana" durante il caricamento di entità correlate.
- Round trip di rete. I round trip si trovano oltre la quantità di dati in transito, poiché il tempo impiegato per l'esecuzione di una query nel database può risultare significativamente inferiore rispetto al tempo necessario per il trasferimento dei pacchetti tra l'applicazione e il database. Il sovraccarico dei round trip dipende in modo significativo dall'ambiente in uso. Più lontano è il server di database, maggiore sarà la latenza e il costo di ogni round trip. Con l'avvento del cloud, le applicazioni si trovano sempre più lontano dal database e le applicazioni "loquaci" che eseguono troppi round trip hanno prestazioni ridotte. È quindi importante comprendere esattamente quando l'applicazione contatta il database, il numero di round trip eseguiti e se tale numero può essere ridotto.
- Sovraccarico del runtime di EF. EF aggiunge infine un sovraccarico del runtime alle operazioni del database: EF deve compilare le query da LINQ a SQL, anche se tale operazione deve essere in genere eseguita una sola volta, il rilevamento delle modifica aggiunge sovraccarico, che tuttavia può essere disabilitato, e così via. In pratica, è probabile che il sovraccarico di EF per le applicazioni concrete sia minimo nella maggior parte dei casi, perché il tempo di esecuzione delle query nel database e la latenza della rete sono i fattori principali che influenzano il tempo totale. È tuttavia importante comprendere quali sono le opzioni disponibili e come evitare alcuni problemi.
Sapere cosa sta succedendo dietro le quinte
EF consente agli sviluppatori di concentrarsi sulla logica di business generando SQL, materializzando i risultati ed eseguendo altre attività. Analogamente a qualsiasi livello o astrazione, tende anche a nascondere ciò che sta succedendo dietro le quinte, ad esempio le query SQL effettive in esecuzione. Le prestazioni non sono necessariamente un aspetto critico di ogni applicazione, ma per le applicazioni per cui sono rilevanti, è fondamentale che lo sviluppatore comprenda quali operazioni vengono eseguite da EF: esaminare le query SQL in uscita, seguire i round trip per assicurarsi che il problema N+1 non si verifichi e così via.
Memorizzazione nella cache all'esterno del database
Infine, il modo più efficiente per interagire con un database consiste nel non interagire affatto con esso. In altre parole, se l'accesso al database provoca un collo di bottiglia delle prestazioni nell'applicazione, può essere utile memorizzare nella cache determinati risultati all'esterno del database, in modo da ridurre al minimo le richieste. Anche se la memorizzazione nella cache aggiunge complessità, è una parte particolarmente importante di qualsiasi applicazione scalabile: mentre il livello applicazione può essere facilmente ridimensionato aggiungendo altri server per gestire un maggiore carico, il ridimensionamento del livello di database è in genere molto più complicato.