SQL Server - Facciamo quattro conti con i dischi
Mi capita spesso di incontrare evidenti problemi di performance sui vari volumi utilizzati da SQL Server, sia per un’errata scelta del livello di RAID sia per un errato dimensionamento del numero di dischi necessari a soddisfare la quantità di operazioni al secondo richieste dai vari database.
Le moderne Storage Area Network dispongono di sistemi di gestione dei volumi che praticamente in tempo reale possono riallocare su meccaniche completamente differenti,anche come numero, quello che dal sistema operativo viene visto come disco (la lettera dell’unità): inoltre la grande quantità di memoria cache installata garantisce prestazioni teoriche tali da non far sorgere preoccupazioni, tant’è che per volumi dedicati alla “semplice” di repository normalmente si può risparmiare su qualche disco.
La tendenza attuale inoltre è quella che porta a vedere la SAN più come una riserva di GigaByte e TeraByte di spazio, piuttosto che come un sistema dal quale si possono estrarre notevoli performance, perché normalmente si chiede “Ho bisogno di XXX Gb di spazio” e non “Ho bisogno di XXX Gb di spazio che sia in grado di garantirmi YYY operazioni di I/O al secondo”.
E qui si inserisce un aspetto molto importante da considerare: SQL Server ( e anche Exchange ) all’avvio richiede l’apertura dei suoi file di database in modalità WriteThrouh, tramite il flag FILE_FLAG_WRITE_THROUGH e FILE_FLAG_NO_BUFFER della funzione Windows CreateFile, ignorando quindi qualsiasi tipo di cache, da quella del sistema operativo a quella che può essere presente sul disco. Solo alcuni sistemi molto avanzati possono essere configurati per ignorare questi due flag e fare in modo che SQL Server “veda” la cache come “stable media” (praticamente un disco) garantendo così elevate performance, ma la maggior parte delle installazioni oggi presenti non è in grado: e qui iniziano i problemi perché dobbiamo fare i conti, nel vero senso del termine, con le pure caratteristiche fisiche dei dischi che compongono gli array (spesso uno solo) dove risiedono i file di dati di SQL Server.
Vediamo quindi un generico disco SCSI di ultima generazione cosa può garantirci come numero di operazioni di I/O prima di iniziare ad accodare le richieste e allungare i tempi di risposta: il riferimento dei nostri calcoli è un Seagate Cheetah 15.5K, disponibile nei tagli 74Gb, 146Gb e 300Gb, i più comuni (purtroppo … come dimensione del singolo) usati oggi all’interno delle SAN.
Il disco in questione presenta per i tre formati le seguenti caratteristiche:
- latenza media di 2ms (considerata come la metà del tempo necessario per compiere un intera rotazione dei piatti)
- Scrittura Random 4.0ms
- Lettura Random 3.5ms
- Scrittura Sequenziale 0.4ms
- Lettura Sequenziale 0.2ms
Tenendo presente che un disco inizia a dare evidenti segni di accodamento quando viene usato in modo continuativo oltre l’ 85% delle sue capacità (Cap. 8 di “Microsoft SQL Server 2000 Performance Tuning Technical Reference”), possiamo passare a valutare quanto rende il nostro Cheetah in termini di operazioni al secondo.
Relazione tra tempi di risposta e percentuale di utilizzo del disco
Abbiamo che in modalità sequenziale una lettura richiede una latenza totale di 2.2ms mentre per una scrittura servono 2.4ms, per un totale rispettivamente di 455 e 417 operazioni/sec (= 1000m / latenza ). In modalità random i valori salgono 5.5ms e 6.0ms ottenendo così 182 e 167 operazioni/sec.
A questi risultati dobbiamo sottrarre l’overhead della catena driver+controller+logica di gestione del disco, in sostanza tutto quello che si trova tra la richiesta di scrittura da parte del sistema operativo e la testina del disco, valutato intorno al 20% per le operazioni random e 10% per quelle sequenziali. I valori calcolati prima diventano così 364 e 333 per l’accesso sequenziale mentre per l’accesso random si scende a 164 e 150: abbiamo così il massimo ammissibile per ogni disco nel caso di uso non continuativo. I valori medi diventano quindi 348 nel caso di accesso sequenziale e 157 per l’accesso random.
Prendendo ora come riferimento l’85% dell’utilizzo onde evitare evidenti accodamenti, i nostri valori diventano 309 e 283 nel primo caso e 139 e 128 nel secondo, con una media rispettivamente di 296 per l’accesso sequenziale e 133 per l’accesso random.
Di seguito la tabella riassuntiva dei dati appena trovati:
Operazioni/Sec | Overhead:20% random, 10% sequenziale | Media per uso non continuativo | 85% per evitare code | Media | |||
455 | 364 | 348 | 309 | 296 | |||
417 | 333 | 283 | |||||
182 | 164 | 157 | 139 | 133 | |||
167 | 150 | 128 |
Vediamo ora i vari livelli di RAID come si comportano dal punto di vista delle operazioni di I/O richieste ad ogni disco che compone un Array.
RAID0
Alla base di tutti i livelli abbiamo il RAID0, un semplice striping che non offre caratteristiche di fault tolerance e naturalmente non è raccomandato per fornire storage a SQL Server, anche se rappresenta il massimo livello di performance. Il numero di operazioni gestite da ogni disco componente l’array è dato da:
I/O per Disco = (Letture + Scritture) / Numero di dischi
RAID1
Rappresenta il “classico” mirroring in cui i il meccanismo di “fault tolerance” viene implementato replicando i dati un secondo disco. dal punto di vista del calcolo abbiamo:
I/O per Disco = [Letture + (2 * Scritture)] / 2
Si nota quindi come in questo caso le operazioni di scrittura siano più onerose, mentre nel caso delle letture grazie al supporto “Split seeks”, che permette di distribuire equamente il carico sui due dischi, si comporta come un RAID0
RAID5
E’ probabilmente il livello più utilizzato perché fornisce un buon livello “fault tolerance” unitamente ad un elevato rapporto numero di dischi/spazio disponibile, e in determinate condizione anche buone performance. Il RAID5 introduce il concetto di “parità” per potere ricostruire il dato nel caso in cui uno dei dischi componenti un array dovesse rompersi: il calcolo della parità risulta essere anche il “tallone d’Achille” per le performance di questa implementazione. Al momento della creazione di un volume di questo tipo la parte riservata ai dati viene inizializzata a 0 ed il bit di parità resettato: per questo motivo sono necessarie quattro operazioni, la prima per leggere il dato esistente, la seconda lo stato della parità, la terza per riscrivere il nuovo dato e la quarta per la nuova parità dopo che il processore del controller ne ha calcolato il valore. Abbiamo quindi:
I/O per Disco = [Letture + (4 * Scritture)] / Numero di dischi
dal momento che come abbiamo visto sopra per ogni operazione di scrittura ne occorrono 4
RAID10
E' l'unica vera alternativa al RAID5 nel momento in cui si vuole il massimo delle performance unitamente al migliore livello di “fault tolerance” ed è in sostanza un mirroring al quale viene aggiunta la funzionalità di stripe.
Il calcolo presenta la stessa formula del RAID0 ma con il denominatore che diventa uguale al numero di dischi (sempre multiplo di 2)
I/O per Disco = [Letture + (2 * Scritture)] / Numero di dischi
Sono previsti anche altri livelli di RAID: 3,4, 6 (un 5 con doppia parità) e 7 ( 3 o 4 con caching), ma il loro impiego è limitato a casi sporadici.
LA DOMANDA FATIDICA: RAID5 o RAID10?
Purtroppo molto spesso le esigenze di spazio dominano nei confronti delle performance ed il RAID5, a parità di dischi fornisce sempre una capacità di memorizzazione superiore, ma una volta che questo problema passa in secondo piano sarebbe opportuno scegliere praticamente sempre il RAID10 dal punto di vista delle performance. Il RAID5 nel momento in cui la percentuale delle scritture supera il 10% del totale inizia a imporre un maggior carico ai singoli dischi dell’array.
Prendiamo ad esempio il nostro disco visto sopra, da 188 operazioni random al secondo prima di iniziare ad accodare, e vediamo cosa succede una volta inserito in un array composto da 6 unità (configurazione molto diffusa), che si trova a gestire 500 operazioni al secondo.
Confronto sul carico dei dischi
Rapporto Lettura/Scrittura |
RAID 5 I/Os [a] |
RAID 10 I/Os [b] |
(500 + 0) / 6 83 I/Os per disco |
(500 + 0) / 6 83 I/Os per disco |
|
(450 + 200) / 6 108 I/Os per disco |
(450 + 100) / 6 91 I/Os per disco |
|
(375 + 500) / 6 145 I/Os per disco |
(375 + 250) / 6 104 I/Os per disco |
|
(250 + 1000) / 10 208 I/Os per disco |
(250 + 500) / 6 125 I/Os per disco |
|
(0 + 2000) / 6 333 I/Os per disco |
(0 + 1000) / 6 166 I/Os per disco |
|
Come si vede già con il 10% di scritture il RAID5 impone più del 15% in più di carico su ogni disco, al 25% il confronto inizia a diventare molto sconveniente, e già prima del 50% supera il limite oltre il quale inizia l’accodamento. infine al 100% di scritture il RAID5 risulta evidentemente in difficoltà mentre il RAID10 si mantiene ancora entro le capacità del singolo disco.
Mauro Munzi
Senior Support Engineer
Microsoft Enterprise SQL Support