Il protocollo di Two Phase Commit

Una funzionalita' molto importante, fornita per altro dalla maggior parte dei DBMS relazionali, e' la gestione delle transazioni (vedi il documento Gestione delle transazioni sulle basi dati relazionali).
La gestione completa delle transazioni deve rispettare le proprieta' ACID (Atomicity, Consistency, Isolation, Durability). Le tecniche utilizzate sono il data logging, l'utilizzo della before image o dei rollback segment, il locking dei diversi oggetti, ... naturalmente con modalita' differenti a seconda del software utilizzato.

Piu' complessa e' la gestione delle transazioni quando queste debbono essere svolte su basi dati distribuite. In questo caso e' necessario l'utilizzo di un particolare protocollo detto Two Phase Commit (2PC). Tale protocollo e' implementato in alcuni sistemi transazionali e, tra gli altri, nell'RDBMS Oracle.

Transazioni

Semplificando molto una transazione e' una serie di modifiche effettuate alla base dati che deve essere eseguita interamente (commit) o per nulla (rollback). Questo deve avvenire in ogni condizione, anche in caso di blocco della base dati, del sistema ospite o di recupero da backup.

Le proprieta' ACID di un sistema transazionale richiedono infatti:

Lo standard ANSI/ISO SQL prevede quattro livelli di isolamento dei dati: Read uncommitted, Read committed, Repeatable reads, Serializable.

La modalita' di implementazione differiscono molto e differenti sono le prestazioni e le funzionalita' fornite... ma comunque tutti i database relazionali implementano in modo completo le transazioni fornendo le proprieta' ACID e garantendo uno o piu' isolation level nella gestione dei dati.

La difficolta' nelle transazioni distribuite

La normale gestione delle transazioni non e' sufficiente quando si effettuano transazioni distribuite.

L'applicazione effettua una modifica sulla base dati A, quindi effettua una modifica sulla base dati B, infine desidera confermare entrambe le operazioni.


A questo punto un semplice messaggio di Commit non consente una gestione corretta della transazione distribuita. Infatti e' possibile che una delle due banche dati, per una qualsiasi ragione, sia stata posta fuori linea dopo l'avvenuta modifica dei dati, e l'operazione di commit eseguita sull'altra banca dati porterebbe ad una situazione inconsistente.

 

Il protocollo Two Phase Commit

Il protocollo 2PC consente la gestione delle transazioni in ambiente distribuito.

Con il protocollo di 2PC il commit dei dati avviene in due passi.

Nel primo passo un "coordinatore" della transazione manda il messaggio di PREPARE TO COMMIT a tutte le basi dati o agenti interessati dalla trasazione. Se tutti rispondono positivamente entro il timeout il coordinatore invia il messaggio di COMMIT; se qualcuno risponde negativamente o non risponde, viene inviato il messaggio di ROLLBACK.

Con tale protocollo il controllo di una transazione distribuita e' completo. In qualsiasi situazione e' determinato univocamente lo stato della transazione.

 

Alcune implicazioni

Il traffico di rete che una transazione in 2PC effettua e' notevolmente maggiore rispetto alle transazioni tradizionali. Il numero di messaggi e' maggiore e, soprattutto, aumenta la latenza.
Ogni transazione deve essere confermata da tutti i nodi che partecipano alla transazione. In caso di un nodo non disponibile per decidere il rollback della transazione deve essere atteso un timeot.

Il tempo di timeout deve essere scelto con attenzione e, generalmente, non e' di breve durata. E' infatti necessario dare tempo sufficiente a tutti i server interessati di rispondere positivamente (anche se carichi o connessi con reti lente); altrimenti verrebbe effettuato il rollback di transazioni che avrebbero potuto essere concluse positivamente.

Poiche' il tempo di timeout non e' di breve durata, in caso di problemi sulla rete e' spesso necessaria una lunga attesa prima che venga effettuato un ROLLBACK e chiusa la transazione.

Se il coordinatore della transazione cade o se la rete non consente il collegamento tra i vari sistemi che ospitano le banche dati le transazioni restano in uno stato attesa. La transazione e' in effetti attiva e verra' risolta sucessivamente.

In tale stato, che puo' essere anche di lunga durata, debbono ovviamente essere mantenuti attivi tutti i lock sulle risorse impegnate dalla transazione. Questo puo' portare alla necessita' di "risolvere" manualmente le transazioni attive in modo da liberare i lock su righe e tabelle.

Poiche' tutti i database presenti in rete vengono coordinati da transazioni in 2PC e' importante mantenere il costante allineamento delle banche dati. Questo deve avvenire anche nel caso di un ripristino da backup di un solo database. Tutti gli altri database in rete debbono essere allineati con la stessa versione dei dati.

Nel caso in cui una risorsa utilizzata non sia attiva ovviamente le transazioni distribuite non possono avere luogo. Per tale ragione i livelli di servizio delle varie banche dati ed i servizi coinvolti nelle transazioni distribuite debbono essere allineati tra loro (eg. backup, attivita' di manutenzione, ...).

 

Two Phase Commit ed Oracle

Oracle, dalla versione 7.0 con l'installazione dell'opzione Distribuite Database Option e con l'utilizzo di SQL*Net 2, fornisce il protocollo di Two Phase Commit e quindi il supporto completo delle transazioni in ambiente distribuito.
Nelle versioni successive non sono state introdotte variazioni significative... attualmente la DDO fa parte delle funzionalita' di base della distribuzione Enterprise.

A parte la corretta installazione dei pacchetti e delle opzioni non e' necessario effettuare altro. Dal punto di vista dell'utilizzo le transazioni distribuite ed il protocollo di 2PC sono semplicissimi e trasparenti. E' sufficiente creare dei database link tra le basi dati e quindi, anche senza definire sinonimi, e' gia' possibile effettuare transazioni distribuite semplicemente effettuando operazioni di modifica su tabelle di istanze diverse. Automaticamente Oracle capisce che si tratta di una transazione distribuita ed utilizza il protocollo 2PC per coordinare le transazioni tra le diverse istanze coinvolte. I "coordinatore" e' tipicamente l'istanza da cui si avvia la transazione.

Dal punto di vista tecnico le cose non sono cosi' semplici...
Il "coordinatore" in terminologia Oracle e' chiamato Commit Point e la scelta, tra i vari nodi che partecipano alla trasazione, e' basata sul parametro COMMIT_POINT_STRENGHT. Se i nodi hanno la stessa "forza" viene scelta l'istanza che ha iniziato la transazione.
Tutte le transazioni in dubbio vengono mantenute sulla vista DBA_2PC_PENDING che e' controllabile da parte del DBA. Utile e' anche la vista DBA_2PC_NEIGHBORS.
Una transazione 2PC puo' rimanere in dubbio a lungo... naturalmente mantenendo lock sui dati ed occupando redo! Puo' essere necessario forzare una transazione con il comando COMMIT FORCE 'trans_ID'; dove il trans_ID e' quello indicato nella DBA_2PC_PENDING. Un comando analogo (ROLLBACK FORCE) e' utilizzato per forzare un rollback.

Anche se l'utilizzo del 2PC e' semplice dal punto di vista applicativo, usare le transazioni distribuite quando non serve e' SBAGLIATO. Il 2PC e' molto piu' pesante di una transazione locale ed introduce una serie di possibili rallentamenti e blocchi alle applicazioni.

 

Per il DBA

In alcuni casi particolari puo' avvenire che le transazioni distribuite non possono essere risolte (eg. il coordinatore della transazione non e' attivo). Come abbiamo visto in questo caso il DBA puo' controllare e quindi risolvere manualmente la transazione.
Sempre a carico del DBA sono alcune configurazioni e l'eventuale tuning...

Alcuni parametri dell'INIT influenzano la gestione delle transazioni distribuite. Tra i principali: DISTRIBUTED_LOCK_TIMEOUT, DISTRIBUTED_RECOVERY_CONNECTION_HOLD_TIME, DISTRIBUTED_TRANSACTIONS, COMMIT_POINT_STRENGTH, ...
Per un numero limitato di transazioni i valori di default sono sufficenti ma con un uso significativo del 2PC vanno monitorati ed eventualmente modificati.

L'uso del 2PC richiede una gestione maggiore da parte del DBA e rende necessario il coordinamento di basi dati diverse... Va utilizzato solo se necessario!

 

XA

Nel 1991 X/Open definisce le specifiche per il Distribuited Transaction Processing (DTP). Oracle si uniforma a tali specifiche fornendo un interfaccia di programmazione: la Oracle XA library.
La libreria XA consente di utilizzare l'RDBMS Oracle come Resource Manager (RM) in transazioni distribuite coordinate da un Transaction Manager (TM) esterno e richieste da un Application Program (AP). Naturalmente tale architettura diventa interessante quando vengono utilizzati diversi RM, eventualmente di fornitori differenti, per realizzare una transazione distribuita e coordinata.
Le funzioni introdotte dallo standard ed implementate da Oracle sono:

XA SubroutineDescrizione
xa_open() Si collega al Resource Manager (RM).
xa_close() Si disconnette dal RM.
xa_start() Inizia una transazione e la associa ad un transaction ID (XID).
xa_end() Disassocia il processo dallo XID.
xa_rollback() Effettua un Rollback della transazione dello XID passato come parametro.
xa_prepare() Prepara la transazione associata allo XID. E' la prima fase del Two Phase Commit (2PC).
xa_commit() Effettua il commit della transazione XID. Questa e' la seconda parte del 2PC.
xa_recover() Restituisce la liste delle transazioni Prepared, Committed o Rolled Back in modo euristico.
xa_forget() Dimentica la transazione associata allo XID.

Un'applicazione XA non utilizza COMMIT, ROLLBACK, statement SQL... ma deve richiedere servizi agli RM e confermare le transazioni al TM. Si tratta quindi di un'applicazione "diversa" dalle normali applicazioni che accedono ad Oracle con le librerie tradizionali.
Nei servizi richiamati si utilizzano i normali statement SQL ma naturalmente non possono essere utilizzati DDL (con Oracle forzano un commit implicito). Inoltre, nel caso in cui vengano acceduti dalla transazione XA oggetti via DB Link, questi ultimi debbono essere ospitati su un'istanza con un listener MTS.

Oracle supporta il 2PC in modo nativo. Se acceduto tramite un'interfaccia XA funziona perfettamente o quasi. Tutte le normali attivita' avvengono correttamente coordinate con il protocollo di Two Phase Commit. Ma per soddisfare completamente lo standard servono un paio di comandi di configurazione, altrimenti in caso di crash del TM il recovery puo' restituire un errore. Ecco i comandi necessari che creano la vista V$XATRANS$:

start $ORACLE_HOME/rdbms/admin/xaview.sql

grant select on v$xatrans$ to public;
grant select on pending_trans$ to public;
grant select on dba_2pc_pending to public;
grant select on dba_pending_transactions to public;
grant execute on dbms_system to user; (>10g R1)

Per il principio del minimo privilegio i GRANT possono essere anche concessi agli utenti specifici che fanno uso delle transazioni distribuite... ma non fa una grande differenza: l'elenco degli XID delle transazioni in dubbio non e' poi cosi' interessante!

Una nuova giovinezza nell'utilizzo delle interfacce XA e' stata portata dai driver JDBC. Questi infatti, dalla versione 2 delle specifiche JDBC, prevedono l'implementazione dell'interfaccia XA. Utilizzando i driver corretti e' possibile utilizzare l'RDBMS Oracle come componente di una transazione XA.
Rispetto alla normale configurazione JDBC l'unica differenza significativa e' l'utilizzo della classe DriverClassname: oracle.jdbc.xa.client.OracleXADataSource, anziche' della classe oracle.jdbc.driver.OracleDriver tutte le altre configurazioni sono identiche (eg. URL: jdbc:oracle:thin:@<server>[:<1521>]:<database_name>).
La paginetta Drivers JDBC Oracle descrive con maggiore dettaglio i driver, le versioni (per disporre dell'interfaccia XA completa occorre usare l'ojdbc14.jar o successivi) ed il loro utilizzo nei piu' comuni ambienti J2EE.
Ecco un esempio di configurazione di un datasource Oracle XA su JBoss:

<?xml version="1.0" encoding="UTF-8"?>

<datasources>
  <xa-datasource>
    <jndi-name>XAOracleDS</jndi-name>
    <track-connection-by-tx/>
    <isSameRM-override-value>false</isSameRM-override-value>

    <xa-datasource-class>oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>
    <xa-datasource-property name="URL">jdbc:oracle:thin:@db.mydomain.it:1569:testdb</xa-datasource-property>
    <xa-datasource-property name="User">scott</xa-datasource-property>
    <xa-datasource-property name="Password">tiger</xa-datasource-property>

    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
    <no-tx-separate-pools/>
      <metadata>
         <type-mapping>Oracle9i</type-mapping>
      </metadata>
  </xa-datasource>
  <mbean code="org.jboss.resource.adapter.jdbc.vendor.OracleXAExceptionFormatter" 
         name="jboss.jca:service=OracleXAExceptionFormatter">
    <depends optional-attribute-name="TransactionManagerService">jboss:service=TransactionManager</depends>
  </mbean>
</datasources>

Il mondo non si ferma: Teorema CAP, noSQL, Big Data

L'ACID e' un argomento sui cui si pensava non ci fosse piu' nulla da dire, perche' riguarda direttamente la consistenza dati ma ... in realta' ha visto un'importante evoluzione in questi ultimi anni!
Infatti la regola ACID (Atomicity, Consistency, Isolation, Durability) e' stata messa in profonda discussione dalla nascita di basi dati cosi' grandi oppure di uso cosi' diffuso da non poter essere poste su un solo server o su un numero limitato di server o addirittura su un solo continente.

Teorema CAP

Oltre al Two Phase Commit, dal punto di vista pratico, sono stati sviluppati nuovi protocolli come: il protocollo di consenso Paxos [NdE: 1998] e le sue ottimizzazioni (eg. Raft 2014), le implementazioni di database NoSQL e Big Data, ...

Dal punto di vista teorico sono stati studiati: il teorema CAP (Consistency, Availability, Partition tolerance) [NdE: 2000], la classificazione dei database in PACELC (on Partition: Availability or Consistency Else: Latency or Consistency), le BASE transactions (Basic Availability, Soft-state, Eventual consistency), ...

L'idea di base del teorema CAP, senza entrare in dettagli ne cercare di essere precisi, e': quando c'e' una partizione tra i nodi che ospitano un database distribuito non si possono avere contemporaneamente disponibilita' e consistenza senza latenza.
La suddivisione in PACELC e' un'estesione al teorema CAP che analizza il comportamento nel caso in cui vi sia un partizionamento della rete scegliendo tra una maggiore latenza o rinunciando alla consistenza.

Sempre semplificando molto, la differenza dal protocollo 2-Phase-Commit ed i piu' recenti protocolli Paxos, Raft, ... e' che anziche' attendere la conferma di tutti i nodi del cluster come nel 2PC ci si accontenta di una maggioranza.

Sul mercato, oltre ai Database relazionali che garantiscono CA (Consistency&Availability: Oracle, MySQL, MSSQL, PostgreSQL, Vertica, Kafka, ...), si sono affiancati in questi anni Database noSQL che rinunciano alla disponibilita' garantendo CP (Consistency&Partition Tolerance: HBase, MongoDB, Redis, Google Bigtable/Spanner, HyperTable, Terrastore, YugabyteDB, CockroachDB, ZooKeeper, ...) e Database noSQL che rinunciano alla consistenza garantendo AP (Availability&Partition Tolerance: CouchDB, Cassandra, DynamoDB, Riak, Voldemort, Blockchain networks, ...).

Vi sono poi configurazioni particolari che consentono di fornire una maggior resilienza ai problemi o che consentono ad prodotto di agire un modo differente. Ad esempio MySQL e' un normale relazionale ed e' quindi un CA, la replica MySQL puo' essere configurata in modalita' AP se si lascia scrivere su uno slave, la soluzione Galera MySQL Cluster e' tipicamente considerata una CP, Google Spanner tecnicamente e' un CP pero' e' praticamente quasi un CA poiche' si basa su una rete interna ad altissima affidabilita', le blockchain networks come quella dei Bitcoin sono AP ma vengono descritte come come eventually consistent ed in effetti si sono verificati casi di rollback anche se rarissimi e risolti in poche ore, ...
Quindi, e questa e' probabilmente la cosa piu' importante, tutti i database distribuiti hanno molteplici possibilita' di configurazione che possono variare il loro comportamento nei casi di partizione di rete, caduta di nodi, costituzione del quorum, conferma delle transazioni, sovraccarico dei server, errori, ... la collocazione teorica in una categoria (eg. CP o PA/EL) non e' sempre fissa e certamente non e' l'unico elemento da considerare per valutare le caratteristiche di un'architettura.

Altre informazioni

Argomenti distinti, ma che presentano aspetti architetturali di interesse, sono:


Testo: Il protocollo di Two Phase Commit
Data: 17 Settembre 1997
Versione: 1.2.3.4 - 14 Febbraio 2017
Autore: mail@meo.bogliolo.name