TrueTime è un orologio distribuito ad alta disponibilità fornito alle applicazioni su tutti i server Google1. TrueTime consente alle applicazioni di generare timestamp che aumentano in modo monotono: un'applicazione può calcolare un timestamp T che è garantito essere maggiore di qualsiasi timestamp T', se T' ha terminato la generazione prima che T abbia iniziato a essere generato. Questa garanzia vale per tutti i server e tutti i timestamp.
Questa funzionalità di TrueTime viene utilizzata da Spanner per assegnare timestamp alle transazioni. Nello specifico, a ogni transazione viene assegnato un timestamp che riflette l'istante in cui Spanner considera che si sia verificata. Poiché Spanner utilizza controllo della contemporaneità multiversione (MVCC), la garanzia di ordinamento dei timestamp consente ai client di Spanner di eseguire letture coerenti in un intero database (anche in più regioni Cloud) senza bloccare le scritture.
Coerenza esterna
Quando il livello di isolamento non è specificato o quando lo imposti come isolamento serializzabile, Spanner fornisce ai client le garanzie di controllo della concorrenza più rigorose per le transazioni, ovvero la coerenza esterna2. In base alla coerenza esterna, il sistema si comporta come se tutte le transazioni venissero eseguite in sequenza, anche se Spanner le esegue su più server (e possibilmente in più data center) per prestazioni e disponibilità superiori. Inoltre, se una transazione viene completata prima che un'altra inizi il commit, il sistema garantisce che i client non possano mai vedere uno stato che include l'effetto della seconda transazione, ma non della prima. Intuitivamente, Spanner non è distinguibile a livello semantico da un database di una singola macchina. Anche se fornisce garanzie così solide, Spanner consente alle applicazioni di ottenere prestazioni paragonabili a quelle dei database che forniscono garanzie più deboli (in cambio di prestazioni più elevate). Per impostazione predefinita, Spanner consente alle scritture di procedere senza essere bloccate da transazioni di sola lettura, ma senza mostrare le anomalie consentite dall'isolamento di lettura ripetibile.
D'altra parte, l'isolamento di lettura ripetibile garantisce che tutte le operazioni di lettura all'interno di una transazione vedano uno snapshot coerente del database così com'era all'inizio della transazione. Questo approccio è utile in scenari di concorrenza di lettura/scrittura elevata in cui numerose transazioni leggono dati che altre transazioni potrebbero modificare. Per ulteriori informazioni, consulta la sezione Isolamento di lettura ripetibile.
La coerenza esterna semplifica notevolmente lo sviluppo delle applicazioni. Ad esempio, supponiamo che tu abbia creato un'applicazione bancaria su Spanner e che uno dei tuoi clienti abbia 50 $nel conto corrente e 50 $nel conto di risparmio. L'applicazione avvia quindi un flusso di lavoro in cui prima esegue il commit di una transazione T1 per depositare 200 $nel conto di risparmio, e poi emette una seconda transazione T2 per addebitare 150 $dal conto corrente. Inoltre, supponiamo che alla fine della giornata i saldi negativi di un account vengano coperti automaticamente da altri account e che un cliente incorra in una sanzione se il saldo totale di tutti i suoi account è negativo in qualsiasi momento della giornata. La coerenza esterna garantisce che, poiché T2 inizia il commit dopo che T1 termina, tutti i lettori del database osserveranno che il deposito T1 è avvenuto prima del prelievo T2. In altre parole, la coerenza esterna garantisce che nessuno vedrà mai uno stato in cui T2 si verifica prima di T1; in altre parole, l'addebito non comporterà mai una sanzione per fondi insufficienti.
Un database tradizionale che utilizza l'archiviazione a versione singola e il blocco rigoroso in due fasi fornisce coerenza esterna. Purtroppo, in un sistema di questo tipo, ogni volta che l'applicazione vuole leggere i dati più recenti (che chiamiamo "lettura coerente"), il sistema acquisisce un blocco di lettura sui dati, che blocca le scritture sui dati in fase di lettura.
Timestamp e MVCC
Per leggere senza bloccare le scritture, Spanner e molti altri sistemi di database mantengono più versioni immutabili dei dati (spesso chiamate controllo della concorrenza multiversione). Un'operazione di scrittura crea una nuova versione immutabile il cui timestamp corrisponde a quello della transazione di scrittura. Una "lettura snapshot" in un timestamp restituisce il valore della versione più recente precedente a quel timestamp e non deve bloccare le scritture. È quindi importante che i timestamp assegnati alle versioni siano coerenti con l'ordine in cui è possibile osservare le transazioni da eseguire. Chiamiamo questa proprietà "timestamping corretto", che equivale alla coerenza esterna.
Per capire perché la corretta registrazione dei timestamp è importante, considera l'esempio bancario della sezione precedente. Senza un timestamp corretto, a T2 potrebbe essere assegnato un timestamp precedente a quello assegnato a T1 (ad esempio, se un sistema ipotetico utilizzasse orologi locali anziché TrueTime e l'orologio del server che elabora T2 fosse leggermente in ritardo). Una lettura dello snapshot potrebbe quindi riflettere l'addebito da T2 ma non il deposito T1, anche se il cliente ha visto il deposito terminare prima di iniziare l'addebito.
Ottenere un timestamp corretto è banale per un database di una singola macchina (ad esempio, puoi semplicemente assegnare timestamp da un contatore globale che aumenta in modo monotono). Ottenerlo in un sistema ampiamente distribuito come Spanner, in cui i server di tutto il mondo devono assegnare timestamp, è molto più difficile da fare in modo efficiente.
Spanner si basa su TrueTime per generare timestamp in aumento monotono. Spanner utilizza questi timestamp in due modi. Innanzitutto, li utilizza come timestamp appropriati per le transazioni di scrittura senza la necessità di comunicazione globale. In secondo luogo, li utilizza come timestamp per le letture coerenti, il che consente di eseguire le letture coerenti in un solo ciclo di comunicazione, anche quelle che interessano più server.
Domande frequenti
Quali garanzie di coerenza fornisce Spanner?
Per impostazione predefinita, Spanner fornisce la coerenza esterna, che è la proprietà di coerenza più rigorosa per i sistemi di elaborazione delle transazioni. Tutte le transazioni in Spanner che utilizzano l'isolamento serializzabile soddisfano questa proprietà di coerenza, non solo quelle all'interno di una partizione. Per ulteriori informazioni, consulta la sezione Panoramica dei livelli di isolamento.
La coerenza esterna afferma che Spanner esegue le transazioni in modo indistinguibile da un sistema in cui le transazioni vengono eseguite in serie e, inoltre, che l'ordine seriale è coerente con l'ordine in cui è possibile osservare il commit delle transazioni. Poiché i timestamp generati per le transazioni corrispondono all'ordine seriale, se un client vede l'inizio del commit di una transazione T2 dopo il completamento di un'altra transazione T1, il sistema assegnerà a T2 un timestamp superiore a quello di T1.
Spanner fornisce la linearizzabilità?
Sì. Per impostazione predefinita, Spanner fornisce una coerenza esterna, che è una proprietà più forte della linearizzabilità, perché quest'ultima non dice nulla sul comportamento delle transazioni. La linearizzabilità è una proprietà di oggetti simultanei che supportano operazioni di lettura e scrittura atomiche. In un database, un "oggetto" in genere è una singola riga o persino una singola cella. La coerenza esterna è una proprietà dei sistemi di elaborazione delle transazioni, in cui i client sintetizzano dinamicamente le transazioni che contengono più operazioni di lettura e scrittura su oggetti arbitrari. La linearizzabilità può essere considerata un caso speciale di coerenza esterna, in cui una transazione può contenere una sola operazione di lettura o scrittura su un singolo oggetto.
Spanner fornisce la serializzabilità?
Sì. Per impostazione predefinita, Spanner fornisce coerenza esterna, che è una proprietà più rigorosa della serializzabilità. Un sistema di elaborazione delle transazioni è serializzabile se esegue le transazioni in modo indistinguibile da un sistema in cui le transazioni vengono eseguite in serie. Spanner garantisce inoltre che l'ordine seriale sia coerente con l'ordine in cui è possibile osservare il commit delle transazioni.
Riprendiamo l'esempio di banking utilizzato in precedenza. In un sistema che fornisce la serializzabilità ma non la coerenza esterna, anche se il cliente ha eseguito T1 e poi T2 in sequenza, il sistema sarebbe autorizzato a riordinarli, il che potrebbe causare l'addebito di una sanzione a causa di fondi insufficienti.
Spanner fornisce una elevata coerenza?
Sì. Spanner fornisce coerenza esterna, che è una proprietà più forte della elevata coerenza. La modalità predefinita per le letture in Spanner è "forte", il che garantisce che osservino gli effetti di tutte le transazioni di cui è stato eseguito il commit prima dell'inizio dell'operazione, indipendentemente dalla replica che riceve la lettura.
Qual è la differenza tra elevata coerenza e coerenza esterna?
Un protocollo di replica mostra una elevata coerenza se gli oggetti replicati sono linearizzabili. Come la linearizzabilità, elevata coerenza è più debole della coerenza esterna, perché non impone nulla sul comportamento delle transazioni.
Spanner fornisce coerenza finale (o pigra)?
Spanner fornisce una coerenza esterna, che è una proprietà molto più forte rispetto alla coerenza finale. La coerenza finale scambia garanzie più deboli con prestazioni più elevate. La coerenza finale è problematica perché significa che i lettori possono osservare il database in uno stato che non è mai esistito (ad esempio, una lettura potrebbe osservare uno stato in cui la transazione B è confermata, ma la transazione A non lo è, anche se A è avvenuta prima di B).
Spanner fornisce letture non aggiornate, che offrono vantaggi di prestazioni simili alla coerenza finale, ma con garanzie di coerenza molto più solide. Una lettura obsoleta restituisce dati di un timestamp precedente, che non può bloccare le scritture perché le versioni precedenti dei dati sono immutabili.