# Basi di dati distribuite

I sistemi a database distribuiti rappresentano una grande sfida per i progettisti di DBMS, in particolare per garantire le proprietà acide e la prevenzione di deadlock.
Possiamo classificare i sistemi distribuiti in due classi:
* Sistemi omogenei: più macchine che contengono la base di dati ma tutte con lo stesso DBMS. È il caso dei database all'interno della stessa organizzazione.
* Sistemi eterogenei: più macchine con DBMS diversi. È il caso (più complicato) di dati che si interfacciano con organizzazioni diverse, che utilizzano a loro volta DBMS differenti.

## Paradigma Client-Server

Il paradigma client-server è uno dei metodi più usati per eseguire operazioni sulle basi di dati. La gestione dei dati è a carico del server che forniscono servizi ai client che li hanno invocati. I client formulano le query e presentano i dati all'applicazione che ne fa richiesta. Normalmente il server è un servizio reattivo cioè effettua operazioni sulla base di dati solamente quando un client ne fa richiesta.
Si noti che il paradigma client-server non implica che il server e il client siano su macchine diverse connesse via rete, possono anche essere sulla stessa macchina e interagire mediante chiamate di sistema.
Ultimamente si è diffusa la architettura three-tier: client-server applicativo-server. Il server applicativo ha la funzione di gestire la logica in comune con tutti i client (che ora vengono chiamati thin-client per il ridotto numero di funzioni). Un esempio di thin-client sono i browser web, che utilizzano applicativi web per connettersi ai server web.

## Tecniche di distribuzione

La tecnica di come distribuire la base di dati (cioè come dividere i dati su differenti macchine fisiche o aree geografiche) può essere scelta essenzialmente in due modi: verticale o orizzontale.
Sia $T$ un'entità della base di dati e $F_{i}$ un suo frammento, allora la tecnica di distribuzione della base di dati deve garantire:
* Completezza: ogni elemento (dato) di $T$ deve essere presente in almeno un frammento $F_{i}$;
* Ricostruibilità: presi tutti i frammenti $F_{i}$ deve essere sempre possibile ricostruire la tabella iniziale $T$

### Frammentazione orizzontale

Nella frammentazione orizzontale ogni frammento consiste in una sequenza di tuple di una relazione, mantenendo l'intero schema della relazione in ogni frammento. Solitamente in questo caso i dati non vengono replicati, così è possibile ricostruire la base di dati semplicemente unendo i vari frammenti.

#### Esempio

Si consideri la relazione:
${\text{ControCorrente}{\underline {codice}}{\text{, filiale, intestatario, saldo, contratto)}}$
la sua frammentazione orizzontale si ottiene ponendo un'operazione di selezione:
${\text{ControCorrente}}_{1}=\sigma _{filiale=1}{\text{ControCorrente}}$
${\text{ControCorrente}}_{2}=\sigma _{filiale=2}{\text{ControCorrente}}$
${\text{ControCorrente}}_{3}=\sigma _{filiale=3}{\text{ControCorrente}}$
Sia ora la relazione: ${\text{Transazioni}{\underline {codice}}{\text{, contocorrente1, contocorrente2, valore, data)}}$
allora possiamo definire i frammenti derivati su una relazione S:
$S_{i}=S\ltimes F_{i}$
quindi:
${\text{Transazione}}_{i}={\text{Transazione}}\ltimes {\text{ControCorrente}}_{i}$
(A sua volta Transazione potrebbe essere distribuita sui vari nodi...)

#### Join distribuito

L'operazione JOIN su basi di dati distribuite è la più onerosa e complessa da gestire. Condizione necessaria affinché il JOIN distribuito sia possibile è che ogni macchina operi sul proprio set di frammenti. Questa condizione è limitativa, si pensi ad esempio un caso in cui un correntista abbia più di un conto corrente, che viene distribuito in frammenti diversi. Inoltre spesso effettuare operazioni tra DBMS diversi complica ulteriormente le cose (vedere sezione sui livelli di trasparenza).

### Frammentazione verticale

Nella frammentazione verticale le relazioni vengono divise per insiemi di attributi. In questo caso è necessario replicare la chiave primaria in ogni frammento, così da permettere la ricostruzione attraverso una JOIN.

## Livelli di trasparenza

I livelli di trasparenza sono parametri che definiscono quanto il programmatore che scrive query per una base di dati distribuita su differenti DBMS può astrarre il suo codice. Questo è dovuto alle differenti tecniche di approccio alla frammentazione, query, ecc. dei vari DBMS.
Possiamo identificare quattro principali livelli di trasparenza:
* Trasparenza di frammentazione: il programmatore non conosce (né gli interessa) come e quando la base di dati è frammentata e distribuita;
* Trasparenza di allocazione: il programmatore conosce la struttura dei frammenti, ma non l'allocazione fisica dei dati;
* Trasparenza di linguaggio: il programmatore conosce sia la struttura dei frammenti che la loro allocazione ed è costretto a indicarle manualmente, però è avvantaggiato dall'avere un solo linguaggio per tutti i DBMS. Si noti che se la maggior parte dei DBMS utilizzi SQL come linguaggio, ogni DBMS ha un proprio dialetto che complicherebbe lo sviluppo in caso di DBMS diversi;
* Nessuna trasparenza.

## Database distribuiti oggi

Ad oggi esistono ancora molti legacy systems, operanti su mainframes con semplici terminali a interfaccia di testo. Nella buona parte di casi, questi sistemi sono sprovvisti di codici sorgente e documentazione, rendendo complesso passare a una base di dati distribuita. Inoltre spesso questi vecchi sistemi (alcuni degli anni 70') sono ancora utilizzati in grandi applicazioni, come banche, servizi finanziari, compagnie aeree, ecc., rendendo il passaggio ancora più difficoltoso.
Un metodo per passare a nuovi sistemi è la creazione di un gateway, cioè un sistema che si interpone tra il nuovo e il vecchio, permettendo l'intercomunicazione finché il passaggio non sarà avvenuto completamente. Questi trasferimenti durano parecchi anni, con costi molto elevati e tempi molto lunghi, soprattutto per garantire l'affidabilità del trasferimento e del nuovo sistema.

## Controllo di concorrenza distribuito

Si rende necessario adottare tecniche per garantire il controllo di concorrenza anche in ambito distribuito. Fortunatamente le proprietà CID rimangono valide anche in ambito distribuito:
* Consistenza: se ogni sottotransazione preserva l'integrità locale, anche i dati globali saranno coerenti e consistenti;
* Isolamento: se ogni sottotransazione è 2PL o TS allora la transazione è globalmente serializzabile;
* Persistenza (Durability): se ogni sottotransazione utilizza il log in formato corretto, la persistenza dei dati globali è garantito.
E l'atomicità? Per garantire l'atomicità tutti i nodi devono decidere ugualmente se effettuare un commit o un abort, tenendo anche conto dei possibili errori di comunicazione che possono avvenire (caduta di un nodo, downlink, ack mancante, ecc.). Per questo si rendono necessari i cosiddetti protocolli di commit.

### Protocollo di commit 2PC

I server della rete vengono classificati in:
* Transaction Manager (TM), un solo server che si occupa di coordinare il protocollo. L'estensione dei record di log per questo server comprende:
** prepare: indica quali nodi della rete partecipano al protocollo
** global commit/abort: indica a tutti i nodi della rete in modo atomico e persistente l'azione da eseguire
** complete: indica la conclusione del protocollo di commit
* Resource Manager (RM), tutti gli altri server, che possiedono i seguenti messaggi/record di log:
** ready: indica la disponibilità a partecipare alla procedura del protocollo
** local commit/abort: indica nel log l'operazione indicata dal TM
Esempio di comunicazione avvenuta con successo:
* TM->RMs: prepare
* RMs->TM: ready
* TM->RMs: global commit/abort
* RMs->TM: ack
* TM solo log: complete

#### Reazione ai guasti

Il protocollo deve essere in grado di rispondere a diversi guasti, tra cui:
* Perdita di un RM: protocollo di ripresa a caldo, dipende dall'ultimo stato presente nel log degli RM (se è abort, sarà effettuata un'operazione di UNDO, altrimenti se è commit REDO). Se il log di un RM non contiene informazioni (è nello stato di ready) dovrà chiedere al TM cosa fare;
* Perdita del TM: se il TM non ha ancora inviato le azioni globali, un global abort viene effettuato. Altrimenti il TM reinvierà tutti i messaggi globali.
* Problemi di rete: se un messaggio viene perso o vi è una separazione della rete, si esauriranno i timeout in attesa dei riscontri e dei ready e un global abort sarà inviato dal TM. Il sistema è comunque in grado di eseguire transazioni che facciano parte della partizione del TM.

## Parallelismo

Dagli anni '90 la forte diffusione di macchine general-purpose, ha portato ad avere sistemi con architettura multiprocessore ma non specifici per basi di dati. Le operazioni sulle basi di dati possono essere eseguite in modo parallelo con una buona efficienza e scalabilità.
Un parametro importante per la misura del carico di lavoro transazionale è il tps (transactions per seconds), utile in particolare per i sistemi OLTP. Invece per i sistemi OLAP si preferisce valutare il carico di lavoro analitico, il quale però può avere un significato diverso nei vari casi (non esiste un'unità di misura comune a tutti).
Per parallellizzare una base di dati, vengono utilizzate principalmente due tecniche:
* inter-query parallelism: le query vengono distribuite (in modo atomico) tra le unità elaborative. Questa tecnica è usata quando alla base di dati vengono richieste spesso piccole ma numerose query;
* intra-query parallelism: ogni query è suddivisa in più parti e distribuita tra le unità elaborative. Ovviamente questa tecnica è adatta a query molto lunghe, come quelle per l'analisi dei dati.
La memoria (di massa e volatile) può essere gestita in modo condiviso (shared memory) oppure a memoria separate (shared nothing), con eventualmente l'uso di una SAN.

## Basi di dati replicate

In molte applicazioni distribuite i dati vengono duplicati in più server o datacenter, per diversi motivi:
* efficienza: un server può accedere alla sua copia locale anziché doverla richiedere a un altro server;
* affidabilità: se la copia principale viene corrotta, un'altra copia può essere usata come backup;
* disponibilità: in caso di guasto a una sola parte del sistema, un'altra parte può fornire il servizio senza interruzioni se ha quella sezione di base di dati mancante.
Il problema di replicare i dati è principalmente il mantenere allineate tutte le copie attraverso la propagazione dei dati, che può avvenire simmetricamente (ogni nodo può provvedere alla modifica della base di dati e informare gli altri nodi) o asimmetricamente (un master effettua le modifiche e informa gli slave).