domenica 25 marzo 2018

ASM - mirroring e failgroup

ASM è un gestore di volumi che supporta le varie configurazioni di Oracle Database e RAC, e fornisce un'alternativa ai tradizionali gestori di volumi, file system e dispositivi raw.

A protezione e prevenzione di eventuali fault sui dischi, ASM fornisce gli strumenti utili al mirroring dei dati.

In ASM i dischi sono organizzati in gruppi (detti DISKGROUP), e per ogni DISKGROUP (alla creazione e non più modificabile) occorre specificarne il livello di ridondanza (mirroring).

I diski di un DISKGROUP sono organizzati in FAILGROUP, che sono dei sottogruppi di dischi. 

A seconda dei livello di ridondanza impostato per il DISKGROUP, occorre un numero minimo di FAILGROUP. In ogni caso, non può esistere un DISKGROUP senza un FAILGROUP. 

Abbiamo 3 livelli di ridondanza:

1) EXTERNAL REDUNDANCY
2) NORMAL REDUNDANCY
3) HIGH REDUNDANCY

Impostare una ridondanza EXTERNAL significa NON mirrorare i dati del diskgroup a livello ASM. In questi casi ci si aspetta che i dischi siano mirrorati a livello storage, o quantomeno sarebbe auspicabile. In caso di disk failure, il DISKGROUP diviene indisponibile finché il disco che ha subito il fault non ritorna a disposizione. In questo tipo di diskgroup, ogni disco appartiene ad un solo FAILGROUP, e si hanno tanti FAILGROUP quanti sono i dischi del DISKGROUP. Tuttavia in questa modalità, non essendoci alcun mirror, quando viene scritto un dato non c'è alcun controllo in merito al FAILGROUP di appartenenza, né vi sono copie di dati. L'unico criterio con cui vengono scritti è quello che essi siano bilanciati equamente su tutti i dischi.

Impostare una ridondanza NORMAL significa avere 2 vie di mirror (due copie dei dati). In questo caso i dischi del diskgroup devono essere separati in almeno 2 FAILGROUP. Oracle consiglia di usarne 3. 

Impostare la ridondanza HIGH significa mirrorare i dati del diskgroup su 3 vie (tre copie). In questo caso i dischi del diskgroup devono essere separati in almeno 3 FAILGROUP. Oracle consiglia di usarne 5.

Un DISKGROUP con ridondanza NORMAL o HIGH è composto da "insiemi di extent" detti extent-set. Ogni extent contiene un insieme di blocchi di dati. Tutti gli extent appartenenti ad un extent-set contengono gli stessi dati. Per la ridondanza NORMAL quindi abbiamo due extent in un extent-set, per quella HIGH ne abbiamo tre.

Un extent dell'insieme viene indicato come "primario". Gli altri come "secondari". L'extent primario viene scritto su un disco del DISKGROUP senza considerarne il FAILGROUP di appartenenza, ovvero in modo tale da garantire l'equo bilanciamento dei dati all'interno di tutto il DISKGROUP. Dopo che il primario è stato allocato, allora vengono allocati i secondari. In questo caso viene scelto un disco il cui FAILGROUP non è stato ancora utilizzato dagli altri extent dell'insieme.

Ciò garantisce due cose:
  • ogni extent di un extent-set appartiene sicuramente ad un solo FAILGROUP;
  • le letture sono sempre spalmate in modo uniforme in tutto il DISKGROUP.
Le letture dei dati infatti sono eseguite sugli extent primari (si usano i secondari solo se i primari sono indisponibili).

Quindi in caso di una ridondanza NORMAL oppure HIGH vengono fatte rispettivamente due o tre copie dei dati. La figura sottostante mostra come vengono distribuiti i dati in caso di ridondanza NORMAL con tre FAILGROUP:


Come si può vedere dall'immagine, ogni blocco di dati è scritto sempre in due FAILGROUP diversi. Questo fa si che il FAULT completo di un intero FAILGROUP non causi una perdita dei dati.

lunedì 28 dicembre 2015

L'Automatic Workload Repository

Come evidente già dal nome, l'Automatic Workload Repositorynon è null'altro che un repository dove vengono memorizzate le statistiche riguardanti il carico di lavoro del database in un intervallo temporale

Dalla figura su stante è possibile osservare attraverso uno schemetto la struttura di tale repository. I dati statistici vengono in prima battuta memorizzati nella SGA. Successivamente ad ogni intervallo temporale (il default è 1 ora) questi dati vengono salvati attraverso delle snapshot (effettuate dal processo MMON) nel tablespace SYSAUX e conservate per un periodo di retention (il default è 8 giorni). 
Sia l'intervallo delle snapshot che la retention sono impostabili a caldo attraverso il package  dbms_workload_repository
Ad esempio se volessimo impostare un intervallo di snapshot di 15 minuti e una retention di 60 giorni, dovremmo lanciare:
execute dbms_workload_repository.modify_snapshot_settings (
     interval => 15,
     retention => 86400);

Per visualizzare le impostazioni correnti, è possibile interrogare la vista dba_hist_wr_control, ad esempio con la seguente query:
select
       extract( day from snap_interval) *24*60+
       extract( hour from snap_interval) *60+
       extract( minute from snap_interval ) "Snapshot Interval",
       extract( day from retention) *24*60+
       extract( hour from retention) *60+
       extract( minute from retention ) "Retention Interval"
from dba_hist_wr_control;

Le viste da cui leggere le statistiche salvate nella SGA piuttosto che negli snapshot sono molte e di non semplice comprensione. Oracle mette a disposizione uno strumento, il report AWR, attraverso il quale sono riportate in maniera più chiara e leggible le statistiche del database per intervallo temporale in formato testo o html. In aggiunta, Oracle esiste un secondo strumento l'Automatic Database Diagnostic Monitor (ADDM) attraverso il quale è possibile ricevere dei consigli "recommendation" sulle modifiche da apportare al database per migliorare le prestazioni ottenute nell'intervallo temporale considerato, ad esempio: 

Per creare un report AWR o un ADDM, si utilizzano due script sql siti nella oracle_home del database:
    report AWR:
    SQL> @?/rdbms/admin/awrrpt.sql 
    report ADDM:
    SQL> @?/rdbms/admin/addmrpt.sql

Dentro un report AWR, i dati sono suddivisi in quattro macro aree:

Time Model:
   v$sys_time_model => dba_hist_sys_time_model
   DB Time (tempo totale speso dal db per le chiamate dalle sessioni principali) 
   Automatic Tracking of Operation Times:
      • Overall parse time (hard, soft, failed,..)
      • SQL, PLSQL and Java overall execution times


Wait Model:
   v$system_event => dba_hist_system_event
   Wait Events categorizzati in base alla Solution Area (Application, concurrency..)

SQL statement statistics:
   v$sqlstats => dba_hist_sqlstat
   Utilizzo delle risorse: Executions, Physical Reads, Physical Writes   Identificazione delle Top SQL usando le differenze nel kernel

OS Stats:
   v$osstat => dba_hist_osstat
   CPU + Memory

Le varie viste utilizzate dall'AWR sono correlate in questo modo:

giovedì 20 febbraio 2014

Gli Access Path, cosa sono

Gli Access Path sono le modalità con cui i dati sono recuperati da un database Oracle. 
Volendone fare un elenco, abbiamo:
  1. Full Table Scans
  2. Rowid Scans
  3. Index Scans
    • Assessing I/O for Blocks, not Rows
    • Index Unique Scans
    • Index Range Scans
    • Index Range Scans Descending
    • Index Skip Scans
    • Full Scans
    • Fast Full Index Scans
    • Index Joins
    • Bitmap Indexes
  4. Cluster Access
  5. Hash Access
  6. Sample Table Scans
Full Table Scans
Questo tipo di scansione legge tutte le righe della tabella filtrando quelle che non rispondono al criterio di selezione dello statement. Oracle legge i blocchi di dati sequenzialmente e siccome questi sono adiacenti, riesce a leggere più blocchi contigui in una singola chiamata I/O grazie al parametro di inizializzazione DB_FILE_MULTIBLOCK_READ_COUNT. 

Oracle utilizza questo tipo di access path nei seguenti casi:
- Mancanza di Indici

- Grandi quantità di dati: se l’ottimizzatore rileva che saranno usati la maggioranza dei blocchi della tabella, allora userà un Full Table Scan anche in presenza di indici.

- Piccole tabelle: quando una tabella occupa meno blocchi di quelli indicati nel parametro DB_FILE_MULTIBLOCK_READ_COUNT sotto l’high water mark, allora il database può leggere i dati in una singola chiamata I/O. 

- Alto grado di parallelismo
Un alto grado di parlallelismo per una tabella costringe l’ottimizzatore a un full table scan. In questo caso esaminare il campo DEGREE della ALL_TABLES per determinare tale grado. 

- Si usa l’hint FULL(alias tabella) 
In questo caso è possibile usare anche l’hint CACHE o NOCACHE per indicare all’ottimizzatore dove mettere i dati recuperati. L’opzione CACHE pone i blocchi nel buffer-cache alla fine dell’LRU list.

- Si esegue la query in parallelo

Rowid Scan
Il rowid è un identificativo di una riga in cui è specificato il datafile e il data-block contenente la riga. Per accedere a una tabella by rowid, oracle prima li deve ottenere dalle righe selezionate dalla clausola where dello statement attraverso un index-scan di uno o più indici della tabella. Dopodiché localizza ogni riga basandosi sul suo rowid. Quindi l’ottimizzatore utilizza un rowid generalmente dopo averlo ricavato da un indice.

Index Scan
In questo metodo, una riga è recuperata scansionando l’indice, usando i valori delle colonne specificate nello statement. Se lo statement richiede solo le colonne dell’indice, allora oracle legge i dati direttamente dall’indice senza accedere alla tabella.  Un indice contiene non solo il valore della colonna indicizzata, ma anche il rowid delle righe della tabella che hanno quel valore. 

Un index scan può essere dei seguenti tipi:
- Index Unique Scan
Questa scansione ritorna al massimo un singolo rowid. Viene eseguito se lo statement contiene un vincolo UNIQUE o PRIMARY KEY che garantisce l’accesso a una sola riga. Può essere utilizzato anche quando nello statement è specificato l’hint INDEX(alias index_name). In tale caso però si specifica di usare l’indice ma non la modalità di accesso (range scan o unique scan).

- Index Range Scan
E’ la classica operazione di accesso ai dati. Essa può essere limitata o non limitata. I dati sono restituiti in ordine ascendente rispetto alle colonne indicizzate. Più righe con gli stessi valori delle colonne sono ordinate in modo ascendente secondo il rowid. Se si utilizza la clausola ORDER BY l’ottimizzatore verifica se l’ordinamento corrisponde a quello di base dell’access path, se è così allora evita il sort. L’ottimizzatore usa un range scan quando trova una o più colonne principali di un indice specificate nella condizione del WHERE. La condizione colonna1 like ‘A%’ non si traduce mai in un range scan. E’ possibile utilizzare l’hint INDEX_DESC() per ottenere un range scan ordinato in senso discendente. 
L’ottimizzatore è influenzato nella sua decisione della percentuale di blocchi a cui accedere, non dal numero di righe. Questa percentuale è detta index clustering factor (indice del fattore di raggruppamento). Questo index clustering factor è una proprietà dell’indice e si riferisce alla diffusione dei valori di una colonna dentro un data-block di una tabella. Un basso indice indica che singole righe sono concentrate in pochi blocchi, al contrario un alto indice indica che singole righe sono “spalmate” randomicamente su più blocchi. Quindi un elevato index clustering factor comporta un aumento del costo del range scan dello statement.

- Index Skip Scan
Spesso la scansione dei blocchi di un indice è più veloce che la scansione di blocchi di tabelle. Questo tipo di access path consente di suddividere un indice composito in sottoindici più piccoli. Nello skip scan la colonna iniziale dell’indice non viene indicata nello statement, quindi il database determina un numero di sottoindici logici per il numero di valori distinti della colonna iniziale. E’ vantaggioso quando la colonna principale dell’indice, quella che viene saltata, non contiene molti valori.

- Full Scan
Un full index scan elimina le operazioni di ordinamento. Esso legge dai singoli blocchi. Oracle può usare tale access path nelle seguenti situazioni: 

  •  Una clausola Order By che corrisponde alle seguenti richieste è presente nella query:
    - Tutte le colonne in Order By sono indicizzate
    - L’ordine delle colonne in Order By è lo stesso delle colonne dell’indice
    - La clausola Order By contiene tutte le colonne dell’indice o un suo sott’insieme.
  • La query richiede un sort merge join. In questo caso il database può eseguire un full index scan al posto di un full table scan, seguito da un ordinamento. Ciò può avvenire nei seguenti casi:
    - Tutte le colonne nella query sono anche nell’indice.
    - L’ordine delle colonne indicate nella query è lo stesso delle colonne principali dell’indice. 
    - Una clausola Group By è presente nella query e le colonne indicate sono presenti nell’indice.

- Fast Full Index Scan
Rappresenta un’alternativa al full table scan quando l’indice contiene tutte le colonne che sono necessarie alla query e almeno una colonna ha un vincolo NOT NULL. Questo tipo di scansione prende i dati dall’indice stesso senza accedere alla tabella. Il database legge l’intero indice mediante letture multiblocco e diversamente dal full index scan, può essere eseguito in parallelo.

E’ possibile specificare il fast full index scan nel parametro di inizializzazione OPTIMIZER_FEATURES_ENABLE oppure con l’hint INDEX_FFS. 

- Index Joins
Un index Joins rappresenta un hash-join di molti indici che insieme contengono tutte le colonne della tabella indicate nella query. Se il database usa questo access path allora l’accesso alla tabella non è necessario in quanto tutti i dati possono essere recuperati dai valori delle colonne degli indici. Non è possibile usare un index join per eliminare le operazioni di ordinamento. L’hint per abilitare tale access path è INDEX_JOIN.

- Bitmap Indexes
Un Bitmap Indexes utilizza un bitmap per i valori chiave e una funzione di mappatura che converte ogni posizione del bit in un rowid. 

Cluster Access
Il cluster scan è usato per recuperare tutte le righe che hanno lo stesso cluster key value da una tabella memorizzata in un cluster indicizzato. In un cluster indicizzato, il database memorizza tutte le righe con lo stesso cluster key value in uno stesso blocco dati. Nell’eseguire un cluster scan, Oracle prima prende i rowid da uno delle righe selezionate dalla scansione sul cluster index, poi localizza le righe basandosi su questo rowid.

Hash Access
In un hash cluster tutte le righe con lo stesso hash value sono memorizzate nello stesso blocco dati. Oracle utilizza un hash scan per localizzare le righe in un hash cluster basandosi su un hash value. Oracle prima calcola l’hash value applicando la funzione di hash al cluster key value specificato nello statement, poi scansione i blocchi contenenti le righe con quell’hash value.

Sample Table Scan
Si tratta di una scansione “a campione” dei dati di una tabella oppure di un complesso statement. Questo access path viene utilizzato quando la clausola from di uno statement include la clausola SAMPLE oppure SAMPLE BLOCK. Per eseguire questa modalità il database legge una specifica percentuale di righe (o blocchi nel caso SAMPLE BLOCK)  della tabella. Esempio: lo statement SELECT * FROM employees SAMPLE BLOCK (1); legge l’1% dei blocchi della tabella.

L’ottimizzatore sceglie l’access path da applicare basandosi su 2 fattori:
  1. Disponibilità dell’access path per quello statement
  2. Stima dei costi di esecuzione usando ogni access path disponibile o una combinazione di questi.

Viene scelto l’access path o combinazione di essi con costo minore.

giovedì 13 febbraio 2014

Il query optimizer di Oracle

Il query optimizer di Oracle (l'ottimizzatore) è un software adibito a determinare il modo più efficiente per eseguire uno statement SQL.

Uno statement SQL infatti può essere eseguito in molti modi, in full-table scan, index scan, nested loop, hash join e l'ottimizzatore sceglie qual è la via migliore per eseguirlo.

Quando un utente lancia uno statement SQL,  l'ottimizzatore compie i seguenti 3 passi:
1) genera un set di potenziali piani di esecuzione basandosi sui percorsi (o metodi) per raggiungere i dati (access paths) e hints.
2) stima i costi di ogni piano di esecuzione elaborato basandosi sulle statistiche del dizionario dati.
3) paragona i diversi piani di esecuzione e sceglie quello con costo minore.

Quindi il suo output è il piano di esecuzione migliore, ovvero una sequenza di combinazioni di passi che il database compirà per eseguire lo statement.

Nel compiere i 3 steps indicati, l'ottimizzatore effettua le seguenti operazioni:
1) Valuta le espressioni e le condizioni dello statement che contengono delle costanti
2) Trasforma lo statement in uno equivalente e più efficiente
3) Sceglie l'obiettivo dell'ottimizzazione
4) Sceglie gli access path per raggiungere i blocchi di dati
5) Sceglie le priorità rispetto alle condizioni di join, ovvero nel caso di statement con condizioni di join, determina quali coppie di tabelle mettere in join prima e quali dopo.

Il componente dell'ottimizzatore che si occupa di trasformare gli statement è il cosiddetto trasformer.
Per l'operazione di trasformazione dello statement in uno equivalente, il trasformer utilizza molte tecniche tra cui:
1) il view merging
2) il predicate pushing
3) il subquery unnesting

Nel view merging ogni vista a cui si fa riferimento nello statement viene espansa dal parser in un blocco query separato. Il blocco rappresenta essenzialmente la definizione della vista, e quindi il risultato di essa. Una delle possibilità per l'ottimizzatore è quello di analizzare questo blocco separatamente e generare un sotto-piano per la vista. L'ottimizzatore elabora quindi il resto della query utilizzando il sotto-piano al fine di generare un piano di esecuzione globale. Questa tecnica di solito porta ad un piano di esecuzione non ottimale perché la vista viene ottimizzata separatamente.

Ad esempio, se per la seguente vista:
CREATE VIEW employees_50_vw AS
SELECT employee_id, last_name, job_id, salary, commission_pct, department_id
FROM employees
WHERE department_id = 50;

si lancia questo statement:
SELECT employee_id
FROM employees_50_vw
WHERE employee_id > 150;

l'ottimizzatore può usare il view marging per trasformare la query su employees_50_vw nella seguente query:
SELECT employee_id
FROM employees
WHERE department_id = 50
AND employee_id > 150;
In pratica si applica alla vista il contenuto di ogni selezione, proiezione o join.

Nel Predicate pushing l'ottimizzatore spinge "pushes" i predicati dal blocco principale al blocco della vista. Ad esempio, si supponga di avere una vista che referenzia due tabelle:
CREATE VIEW all_employees_vw AS
( SELECT employee_id, last_name, job_id, commission_pct, department_id
FROM employees )
UNION
( SELECT employee_id, last_name, job_id, commission_pct, department_id
FROM contract_workers );

lo statement
SELECT last_name
FROM all_employees_vw
WHERE department_id = 50;

viene trasformato nel seguente:
SELECT last_name
FROM ( SELECT employee_id, last_name, job_id, commission_pct, department_id
FROM employees
WHERE department_id=50
UNION
SELECT employee_id, last_name, job_id, commission_pct, department_id
FROM contract_workers
WHERE department_id=50 );

Nel subquery unnesting l'ottimizzatore in pratica trasforma una query innestata in una equivalente non innestata.
Ad esempio lo statement:
SELECT *
FROM sales
WHERE cust_id IN ( SELECT cust_id FROM customers );

viene trasformato nel seguente equivalemte:
SELECT sales.*
FROM sales, customers
WHERE sales.cust_id = customers.cust_id;

Dopo la trasformazione dello statement, vengono generati i diversi piani di esecuzione e l'ottimizzatore provvede al calcolo del costo computazionale dello statement mediante una stima.

Questa stima genera tre differenti tipi di misure:
1) Selectivity
Rappresenta una porzione di righe in un insieme di righe. Essa è legata al predicato della query, ad esempio last_name='Smith', o a una combinazione di predicati. Un predicato filtra uno specifico numero di righe di un insieme. Il valore della Selectivity può variare da 0.0 a 1.0. Il valore 0.0 indica che non ci sono righe che rispondono ai predicati, mentre un valore di 1.0 indica che tutte le righe dell'insieme rispondono ai predicati.

2) Cardinality
Rappresenta il numero di righe in un insieme. L'insieme può essere una tabella, una vista, o il risultato di una join o di un operatore GROUP BY.

3) Cost
Rappresenta l'unità di lavoro o di risorsa utilizzato in una operazione. Una operazione è da intendersi come una scansione di una tabella, l'accesso alle righe di una tabella mediante indice, il join tra due tabelle, o l'ordinamento di un insieme. L'ottimizzatore considera l'I/O, l'uso di CPU, l'uso della memoria come unità di lavoro. Quindi il costo è il numero di unità di lavoro che si prevede di sostenere quando il database esegue una query producendone i risultati.

Il piano di esecuzione che ha un costo minore sarà quello scelto dall'ottimizzatore per eseguire lo statement.

mercoledì 12 febbraio 2014

Hard parse e Soft parse

Quando un codice applicativo viene eseguito, Oracle verifica se questi sia già stato usato precedentemente verificandone la presenza nella library cache. Se esiste e può essere condiviso allora Oracle riusa il codice già esistente. Questo approccio è detto soft parse o library cache hit.
Se, al contrario, il codice non c'è, allora il database deve costruire un nuova versione eseguibile di tale codice. Questo approccio è detto hard parse o library cache miss.

Il library cache miss può verificarsi sia in fase di analisi che in fase di esecuzione. 

Quando un'applicazione effettua una parse call per uno statement, se la parsed representation dello statement non esiste nella library cache, allora Oracle analizza il nuovo statement e memorizza la sua parsed form nella shared pool. 

E' possibile ridurre il library cache miss sulle parse call assicurandosi che tutti gli statement SQL siano presenti nella shared pool, ogni volta che sia possibile.

Se un'applicazione esegue una execute call di un statement SQL e se la porzione eseguibile dello statement, costruita precedentemente, è stata deallocata dalla library cache per fare spazio a un altro statement, allora Oracle rianalizza lo statement implicitamente creando una nuova shared SQL area e quindi lo esegue.

E' possibile ridurre il library cache miss sulle execution calls allocando più memoria alla library cache.

L'hard parse consuma molte più risorse del soft parse.

Le risorse richieste per un soft parse sono:
CPU, library cache latch gets.

Le risorse richieste per un soft parse sono:
CPU, library cache latch gets, shared pool latch gets.

Schematicamente, si può così sintetizzare quanto detto:
1) parsing statement (controllo sintassi e semantica)
2) hashing statement (restituisce un hash value)
3) controllo esistenza hash value in library cash
..3.1) se l'hash value esiste allora Oracle verifica se lo statement in library cash è identico a quello in analisi
......3.1.1) i due statement sono identici:
soft parse: esegui lo statement mediante il piano di esecuzione già associato.
......3.1.2) i due statement non sono identici:
hard parse: genera alberatura e piano di esecuzione associato allo statement
..3.2) se l'hash value non esiste
hard parse: genera alberatura e piano di esecuzione associato allo statement
..3.3) esegui statement mediante piano di esecuzione associato.