La prossima conferenza HighLoad++ si svolgerà il 6 e 7 aprile 2020 a San Pietroburgo.
Dettagli e biglietti . HighLoad++ Siberia 2019. Sala «Krasnoyarsk». 25 giugno, 12:00. Sintesi e .

Capita che i requisiti pratici siano in conflitto con la teoria, dove non sono considerati aspetti importanti per un prodotto commerciale. In questa presentazione viene illustrato il processo di selezione e combinazione di vari approcci per la creazione di componenti per la consistenza causale, basandosi su ricerche accademiche e sulle esigenze di un prodotto commerciale. Gli ascoltatori apprenderanno gli approcci teorici esistenti riguardo agli orologi logici, al tracciamento delle dipendenze, alla sicurezza dei sistemi, alla sincronizzazione degli orologi e sul perché MongoDB abbia scelto determinate soluzioni.
Mikhail Tyulenev (di seguito – MT): – Parlerò della consistenza causale: è una funzione su cui abbiamo lavorato in MongoDB. Lavoro nel team dei sistemi distribuiti, l'abbiamo realizzata circa due anni fa.

Durante il processo, ho dovuto familiarizzare con una grande quantità di ricerche accademiche, poiché questa funzionalità è stata ampiamente studiata. Ho scoperto che nessun articolo si adatta a ciò che è richiesto in produzione, data la presenza di requisiti piuttosto specifici che si trovano in praticamente tutte le applicazioni di produzione.
Parlerò di come noi, in qualità di consumatori di ricerche accademiche, prepariamo qualcosa che possiamo poi presentare ai nostri utenti come un piatto pronto da utilizzare in modo comodo e sicuro.
Coerenza causale (Causal consistency). Definiamo i concetti
Per iniziare, voglio spiegare brevemente cosa sia la coerenza causale. Ci sono due personaggi – Leonard e Penny (dalla serie "The Big Bang Theory"):

Supponiamo che Penny si trovi in Europa e Leonard voglia farle una sorpresa, una festa. E non gli viene in mente nulla di meglio che toglierla dalla lista degli amici e inviare a tutti gli amici un aggiornamento nel feed: «Facciamo una sorpresa a Penny!» (lei è in Europa, mentre dorme, non vede tutto questo e non può vederlo perché non è lì). Alla fine, elimina questo post, rimuove dal «Feed» e ripristina l'accesso, in modo che non si accorga di nulla e non ci siano scandali.
Tutto ciò è fantastico, ma supponiamo che il sistema sia distribuito e che gli eventi non siano andati del tutto come previsto. Può succedere, ad esempio, che la restrizione dell'accesso a Penny sia avvenuta dopo che questo post è apparso, se gli eventi non sono connessi da legami di causa ed effetto. In effetti, questo è un esempio di quando è necessaria la coerenza causale per eseguire una funzione aziendale (in questo caso).
In realtà, queste sono proprietà piuttosto complesse del database – poche di esse sono supportate. Passiamo ai modelli.
Modelli di coerenza (Consistency Models)
Cos'è esattamente un modello di coerenza nei database? Si tratta di alcune garanzie che un sistema distribuito fornisce riguardo ai dati che un cliente può ricevere e in quale sequenza.
In sostanza, tutti i modelli di coerenza si riducono a quanto un sistema distribuito assomigli a un sistema che funziona, ad esempio, su un singolo nodo su un laptop. Ecco quanto un sistema che opera su migliaia di nodi geograficamente distribuiti si avvicina a un laptop, dove tutte queste proprietà vengono sostanzialmente eseguite in modo automatico.
Pertanto, i modelli di coerenza si applicano esclusivamente ai sistemi distribuiti. Tutti i sistemi esistenti in precedenza che operavano su uno scale-up verticale non presentavano tali problemi. Lì c'era una sola Buffer Cache, da cui si estraevano sempre i dati.
Modello Strong
In effetti, il primo modello è il Strong (o linea di risalita, come viene spesso chiamato). Questo è un modello di coerenza che garantisce che ogni modifica, non appena si riceve la conferma che è avvenuta, diventa visibile a tutti gli utenti del sistema.
Questo crea un ordine globale di tutti gli eventi nel database. Questa è una proprietà di coerenza molto forte e in generale è molto costosa. Tuttavia, è ben supportata. È semplicemente molto costosa e lenta – infatti è raramente utilizzata. Questo è chiamato 'rise ability'.
C'è un'altra proprietà, ancora più forte, che è supportata in 'Spanner' – si chiama Coerenza Esterna. Di questo ne parleremo più tardi.
Causale
Il prossimo è Causale, proprio quello di cui stavo parlando. Tra Strong e Causale ci sono diversi sotto-livelli, di cui non parlerò, ma tutti si riconduscono a Causale. Questo è un modello importante, perché è il più forte di tutti i modelli, la coerenza più forte in presenza di reti o partizioni.
I Causali sono in realtà situazioni in cui gli eventi sono legati da una relazione di causa ed effetto. Spesso vengono percepiti come le 'Read your own rights' dal punto di vista del cliente. Se un cliente ha osservato certi valori, non può vedere i valori che erano nel passato. Inizia a vedere letture prefissate. Tutto si riduce a questo.
Causals come modello di coerenza – una parziale ordinazione degli eventi sul server, in cui gli eventi da tutti i client vengono osservati nella stessa sequenza. In questo caso – Leonardo e Penny.
Eventual
Il terzo modello è l'Eventual Consistency. Questa è supportata da praticamente tutti i sistemi distribuiti, essendo il modello minimo che ha senso. Essa implica quanto segue: quando ci sono alcune modifiche nei dati, in un certo momento diventano coerenti.
In quel momento non dice nulla, altrimenti si trasformerebbe in External Consistency – sarebbe una storia completamente diversa. Tuttavia, questo è un modello molto popolare, il più comune. Per impostazione predefinita, tutti gli utenti dei sistemi distribuiti utilizzano proprio l'Eventual Consistency.
Vorrei fornire alcuni esempi comparativi:

Cosa significano queste frecce?
- Latency. Aumentando la forza di consistenza, diventa più grande per motivi comprensibili: è necessario registrare di più, ottenere conferma da tutti gli host e nodi coinvolti nel cluster che i dati sono già presenti. Pertanto, nella Consistenza Eventuale, la risposta è la più veloce, poiché generalmente è possibile anche effettuare un commit in memoria e questo sarà, in linea di principio, sufficiente.
- Disponibilità. Se questo è inteso come la capacità del sistema di rispondere in presenza di interruzioni di rete, partizioni o altri guasti, la resilienza aumenta riducendo il modello di consistenza, poiché è sufficiente che un host funzioni e fornisca alcuni dati. La Consistenza Eventuale non garantisce nulla riguardo ai dati: potrebbe essere qualsiasi cosa.
- Anomalie. Tuttavia, aumenta naturalmente il numero di anomalie. Nella Strong Consistency non dovrebbero praticamente essercene affatto, mentre nella Eventual Consistency possono presentarsi in qualsiasi forma. Si pone la domanda: perché le persone scelgono l'Eventual Consistency se contiene anomalie? La risposta è che i modelli di Eventual Consistency sono praticabili e le anomalie esistono, ad esempio, per brevi intervalli di tempo; esiste la possibilità di utilizzare un master per la lettura e ottenere dati abbastanza consistenti; spesso c'è l'opportunità di utilizzare modelli di forte consistenza. Praticamente funziona e, spesso, il numero di anomalie è limitato nel tempo.
Teorema CAP
Quando sentite le parole consistenza e disponibilità, cosa vi viene in mente? Esatto, il teorema CAP! Voglio sfatare un mito... Non sono io, ma è Martin Kleppmann, che ha scritto un ottimo articolo e un ottimo libro.

Il teorema CAP è un principio formulato negli anni 2000, riguardante Consistenza, Disponibilità, Partizioni: prendi due qualunque, e non puoi scegliere tre. Era un principio di questo tipo. È stato dimostrato come teorema alcuni anni dopo, ad opera di Gilbert e Lynch. Da allora è stato utilizzato come mantra: i sistemi sono stati divisi in CA, CP, AP e così via.
Questo teorema è stato dimostrato in realtà per i seguenti casi... Prima di tutto, la Disponibilità non è stata considerata come un valore continuo da zero a cento (0 – sistema "mortale", 100 – risponde rapidamente; noi tendiamo a vederla in questo modo), ma come una proprietà dell'algoritmo che garantisce che in tutte le sue esecuzioni restituisce dati.
Non si parla affatto del tempo di risposta! C'è un algoritmo che restituisce dati dopo 100 anni – è un algoritmo disponibile meraviglioso, che fa parte del teorema CAP.
In secondo luogo: il teorema è stato dimostrato per variazioni nei valori di una stessa chiave, considerando che queste variazioni sono una linea ridimensionabile. Questo significa che in realtà non vengono praticamente utilizzati, poiché i modelli sono altri, come la Consistenza Eventuale, la Consistenza Forte (forse).
A cosa serve tutto questo? Serve a capire che il teorema CAP, nella forma in cui è stato dimostrato, è praticamente inapplicabile e viene raramente utilizzato. Nella sua forma teorica, limita in qualche modo tutto. Si tratta di un principio che è intuitivamente corretto, ma che non è dimostrato in generale.
La coerenza causale – il modello più forte
Quello che sta accadendo adesso è che è possibile ottenere tutte e tre le cose: Coerenza, Disponibilità e gestire le Partizioni. In particolare, la coerenza causale è il modello di coerenza più forte, che continua a funzionare anche in presenza di Partizioni (interruzioni nella rete). Per questo suscita tanto interesse, ed è per questo che ci siamo dedicati a essa.

In primo luogo, semplifica il lavoro degli sviluppatori di applicazioni. In particolare, offre un ampio supporto dal server: quando tutte le operazioni effettuate da un singolo cliente arrivano in una sequenza garantita all'altro cliente. In secondo luogo, resiste alle partizioni.
La cucina interna di MongoDB
Ricordando che è ora di pranzo, ci spostiamo in cucina. Vi parlerò del modello di sistema, cioè di cosa sia MongoDB per coloro che sentono parlare di questo tipo di database per la prima volta.


MongoDB (di seguito – «MongoDB») è un sistema distribuito che supporta la scalabilità orizzontale, ovvero lo sharding; e all'interno di ogni shard supporta anche la ridondanza dei dati, cioè la replica.
Lo sharding in «MongoDB» (database non relazionale) esegue un bilanciamento automatico, ovvero ogni collezione di documenti (o «tabella» nei termini dei dati relazionali) viene suddivisa in parti, e il server si occupa automaticamente di spostarle tra gli shard.
Il Query Router, che distribuisce le richieste, per il cliente rappresenta un'interfaccia attraverso cui lavora. Sa già dove si trovano i dati e li indirizza verso il corretto shard.
Un altro punto importante: MongoDB è un single master. C'è un Primary – può acquisire registrazioni che supportano le chiavi che contiene. Non è possibile effettuare scritture Multi-master.
Abbiamo rilasciato la versione 4.2 – ci sono nuove funzionalità interessanti. In particolare, abbiamo integrato Lucene – per la ricerca – direttamente in «MongoDB», rendendo possibile eseguire ricerche attraverso Lucene, simili a quelle in «Elasticsearch».
Abbiamo creato un nuovo prodotto – Charts, che è disponibile anche su «Атлас» (il nostro Cloud «Монго»). Offrono un Free Tier – puoi provare a giocare con questo. Charts mi è piaciuto molto – visualizzazione dei dati, molto intuitiva.
Ingredienti della coerenza causale
Ho contato circa 230 articoli pubblicati su questo tema – da Leslie Lampert. Ora cercherò di ricordarne alcune parti dai miei appunti.

Tutto è iniziato con un articolo di Leslie Lampert, scritto negli anni '70. Come puoi vedere, ci sono ancora ricerche in corso su questo argomento. Attualmente, la coerenza causale sta suscitando interesse a causa dello sviluppo dei sistemi distribuiti.
Limitazioni
Quali sono i limiti? Questo è uno dei punti principali, infatti i limiti imposti dai sistemi di produzione differiscono notevolmente da quelli presenti negli articoli accademici. Spesso risultano piuttosto artificiali.

- Innanzitutto, «МонгоДБ» è un master singolo, come ho già detto (questo semplifica notevolmente).
- Riteniamo che il sistema debba supportare circa 10.000 shard. Non possiamo prendere decisioni architetturali che limiterebbero chiaramente questo valore.
- Abbiamo una soluzione cloud, ma crediamo che l'utente debba avere la possibilità di scaricare il binary, avviarlo sul proprio laptop e farlo funzionare senza problemi.
- Riteniamo che nel Research venga raramente utilizzato: i clienti esterni possono fare qualsiasi cosa. MongoDB è open source. Di conseguenza, i clienti possono essere abbastanza intelligenti, ma anche vendicativi – potrebbero voler rompere tutto. Pensiamo che possano verificarsi dei Byzantine Failures.
- Per i clienti esterni, che si trovano al di fuori del perimetro – un'importante restrizione: se questa funzione è disattivata, non ci devono essere degradazioni delle performance.
- Un altro punto – del tutto antiaccademico: compatibilità tra versioni precedenti e future. I vecchi driver devono supportare i nuovi aggiornamenti e il database deve supportare i vecchi driver.
Insomma, tutto ciò impone delle limitazioni.
Componenti di Causal consistency
Ora parlerò di alcuni componenti. Se consideriamo la coerenza causale, possiamo distinguere dei blocchi. Abbiamo scelto tra i lavori che appartengono a un certo blocco: Dependency Tracking, la scelta dell'orologio, come questi orologi possono essere sincronizzati tra loro e come garantiamo la sicurezza – questo è un piano generico di ciò di cui parlerò:

Tracciamento completo delle dipendenze (Full Dependency Tracking)
A cosa serve? Serve affinché, quando i dati vengono replicati, ogni registrazione, ogni modifica dei dati contenga informazioni su quali modifiche dipende. La modifica più semplice e ingenua è quando ogni messaggio che contiene una registrazione include informazioni sui messaggi precedenti:

In questo esempio il numero tra parentesi graffe rappresenta i numeri delle registrazioni. A volte queste registrazioni con valori vengono trasmesse anche per intero, a volte vengono trasmessi determinati versioni. La sostanza è che ogni modifica contiene in sé informazioni sulla precedente (implicitamente porta tutto questo con sé).
Perché abbiamo deciso di non adottare questo approccio (tracciamento completo)? È ovvio che questo metodo è poco pratico: qualsiasi modifica in un social network dipende da tutte le modifiche precedenti in quel social network, passando, ad esempio, da "Facebook" o "VKontakte" in ciascun aggiornamento. Tuttavia, ci sono molti studi su Full Dependency Tracking – questi sono presocial networks, e in alcune situazioni può funzionare davvero.
Tracciamento delle dipendenze esplicito (Explicit Dependency Tracking)
Il successivo è più limitato. Qui si considera anch'esso il trasferimento di informazioni, ma solo quelle che dipendono esplicitamente. Ciò da cui dipende, di norma, è già definito dall'Application. Quando i dati vengono replicati, durante la richiesta vengono forniti solo risposte quando le dipendenze precedenti sono state soddisfatte, ovvero mostrate. Questo è il nocciolo di come funziona la coerenza causale.

Essa vede che la registrazione 5 dipende dalle registrazioni 1, 2, 3, 4 – quindi, attende prima che il cliente acceda alle modifiche apportate dal provvedimento di accesso di Penny, quando tutte le modifiche precedenti sono già passate nel database.
Anche questo non ci soddisfa, perché ci sono comunque troppe informazioni, e questo rallenterà. C'è un altro approccio…
Orologi di Lamport (Lamport Clock)
Sono molto antichi. Il Lamport Clock implica che queste dipendenze si comprimano in una funzione scalare, la quale è nota come Lamport Clock.
Una funzione scalare è un numero astratto. Spesso viene chiamato tempo logico. Ad ogni evento, questo contatore aumenta. Il contatore, conosciuto attualmente dal processo, invia ogni messaggio. È chiaro che i processi possono essere dissincronizzati, avere orari completamente diversi. Tuttavia, attraverso questo scambio di messaggi, il sistema bilancia in qualche modo gli orologi. Cosa succede in questo caso?
Ho diviso quel grande shard in due per chiarire: i Friends possono vivere in un nodo che contiene un pezzo della collezione, mentre il Feed può trovarsi in un altro nodo, che contiene un'altra parte di questa collezione. È chiaro come possano non essere in coda? Prima il Feed dirà: «Replicato», e poi i Friends. Se il sistema non garantisce che il Feed non venga mostrato finché le dipendenze dei Friends nella collezione Friends non siano state consegnate, ci troveremo esattamente nella situazione di cui ho parlato.
Vedete come aumenta logicamente il tempo del counter sul Feed:

Così, la principale proprietà di questo Lamport Clock e della Causal consistency (spiegata attraverso il Lamport Clock) è la seguente: se abbiamo eventi A e B, e l'evento B dipende dall'evento A *, allora segue che il LogicalTime dell'Evento A è minore del LogicalTime dell'Evento B.
* A volte si dice anche che A è accaduto prima di B, cioè A è successa prima di B – questo è un certo rapporto che ordina parzialmente l'intero insieme di eventi che si sono verificati.
Al contrario, è sbagliato. Questo è davvero uno dei principali svantaggi del Lamport Clock: l'ordinamento parziale. C'è il concetto di eventi simultanei, ovvero eventi in cui né (A è accaduto prima di B) né (A è accaduto dopo B). Un esempio può essere l'aggiunta da parte di Leonard di un'altra persona tra gli amici (non Leonard, ma Sheldon, per esempio).
Questo è proprio il principio che viene frequentemente utilizzato quando si lavora con i Lamport Clock: si guarda precisamente alla funzione e da essa si trae la conclusione che tali eventi possono essere dipendenti. Perché in un verso è vero: se LogicalTime A è minore di LogicalTime B, allora B non può essere accaduto prima di A; mentre se è maggiore, allora potrebbe esserlo.
Orologi vettoriali (Vector Clock)
Lo sviluppo logico degli orologi di Lamport è rappresentato dagli Orologi Vettoriali. Si distinguono perché ogni nodo in questo caso contiene i propri orologi separati, che vengono trasmessi come un vettore.
In questo caso, si vede che l'indice zero del vettore corrisponde a Feed, mentre il primo indice del vettore è per Friends (ognuno di questi nodi). E ora saranno incrementati: l'indice zero 'Feed' aumenta durante la registrazione – 1, 2, 3:

Cosa rende migliori gli orologi vettoriali? Il fatto che permettano di capire quali eventi si svolgono simultaneamente e quando avvengono su diverse nodi. Questo è molto importante per un sistema di sharding, come MongoDB. Tuttavia, non lo abbiamo scelto, anche se è una cosa eccellente, funziona splendidamente e probabilmente sarebbe stata utile per noi…
Se abbiamo 10.000 shard, non possiamo trasmettere 10.000 componenti, anche se comprimiamo o troviamo altre soluzioni: il carico utile sarà comunque di gran lunga inferiore al volume totale di questo vettore. Quindi, con molte esitazioni, ci siamo allontanati da questo approccio e ci siamo spostati su un altro.
Spanner TrueTime. Orologi atomici
Ho detto che parlerò di Spanner. È una cosa incredibile, proprio del XXI secolo: orologi atomici, sincronizzazione GPS.
Qual è l'idea? Spanner è il sistema di Google, che di recente è diventato anche accessibile agli utenti (hanno aggiunto SQL). Ogni transazione ha un proprio time stamp. Poiché il tempo è sincronizzato*, a ciascun evento può essere assegnato un determinato orario - gli orologi atomici hanno un tempo di attesa, dopo il quale si verifica garantitamente un altro orario.

In questo modo, registrando semplicemente nel database e aspettando un certo periodo di tempo, si garantisce automaticamente la serializzabilità degli eventi. Hanno il modello di coerenza più forte che si possa immaginare – una coerenza esterna.
* Questo è il problema principale degli orologi di Lamport – non sono mai sincronizzati nei sistemi distribuiti. Possono divergere, anche con NTP non funzionano molto bene. Il "Spanner" ha orologi atomici e sincronizzazione, sembra a livello di microsecondi.
Perché non abbiamo scelto? Non presupponiamo che i nostri utenti abbiano orologi atomici integrati. Quando saranno presenti, integrati in ogni laptop, ci sarà una sincronizzazione GPS superveloce – allora sì… Ma per ora, la migliore opzione possibile è "Amazon", le stazioni base – per i fanatici… Pertanto, abbiamo utilizzato orologi diversi.
Orologi ibridi (Hybrid Clock)
Questo è in realtà ciò che ticchetta in "MongoDB" per garantire la coerenza causale. In cosa sono ibridi? L'ibrido è un valore scalare, ma è composto da due componenti:

- La prima – è l'epoca unix (quante secondi sono trascorsi dall'inizio del "mondo computazionale").
- Secondo: un certo incremento, anch'esso un intero unsigned a 32 bit.
Questo è tutto. C'è un approccio in cui la parte che tiene traccia del tempo si sincronizza costantemente con l'orologio; ogni volta che avviene un aggiornamento, questa parte si sincronizza con l'orologio e si verifica che il tempo sia sempre più o meno corretto, mentre l'incremento consente di distinguere gli eventi che si sono verificati nello stesso momento.
Perché è importante per MongoDB? Perché consente di effettuare backup e ripristini a un certo punto nel tempo, ossia l'evento viene indicizzato nel tempo. Questo è importante quando sono necessari alcuni eventi; per un database, gli eventi sono le modifiche nel database che si sono verificate in determinati intervalli di tempo.
La ragione principale te la dirò solo a te (per favore, non dirlo a nessuno)! Abbiamo fatto così perché così appaiono i dati ordinati e indicizzati nel MongoDB OpLog. L'OpLog è una struttura dati che contiene tutte le modifiche nel database: prima vengono inserite nell'OpLog e poi vengono applicate al Storage, nel caso in cui si tratti di dati replicati o shard.
Questa era la ragione principale. Ci sono anche requisiti pratici per lo sviluppo della base, il che significa che deve essere semplice: poco codice, il minor numero possibile di elementi rotti da riscrivere e testare. Il fatto che i nostri oplog siano stati indicizzati da orologi ibridi è stato molto utile e ci ha permesso di fare la scelta giusta. Questo si è rivelato davvero efficace e ha in qualche modo funzionato magicamente al primo prototipo. È stato davvero fantastico!
Sincronizzazione degli orologi
Esistono diversi metodi di sincronizzazione descritti nella letteratura scientifica. Parlo di sincronizzazione quando abbiamo due shard diversi. Se c'è un replica set, non c'è bisogno di sincronizzazione: è un 'singolo master'; abbiamo un OpLog, in cui tutte le modifiche vengono registrate – in questo caso tutto è già ordinato sequenzialmente nell'OpLog. Ma se abbiamo due shard diversi, la sincronizzazione temporale è importante. Qui gli orologi vettoriali sono stati di grande aiuto! Ma noi non li abbiamo.

Il secondo è "Heartbeats". Si possono scambiare alcuni segnali che avvengono a intervalli di tempo regolari. Ma i "Heartbeats" sono troppo lenti, non possiamo garantire la latenza al nostro cliente.
Il tempo reale è chiaramente una cosa straordinaria. Ma, di nuovo, è probabilmente il futuro... Anche se in "Atlas" è già possibile farlo, ci sono già sincronizzatori di tempo rapidi di tipo "Amazon". Ma non sarà accessibile a tutti.
Il Gossiping è quando tutti i messaggi includono un timestamp. È fondamentalmente ciò che usiamo. Ogni messaggio tra i nodi, driver, router di nodi dati, assolutamente tutto per "MongoDB" – sono elementi, componenti del database, che contengono orologi che scorrono. Hanno ovunque un valore di tempo ibrido, che viene trasmesso. 64 bit? È possibile, si può fare.
Come funziona tutto questo insieme?
Qui prendo in considerazione un replica set per semplificare. C'è il Primary e il Secondary. Il Secondary esegue la replica e non è sempre completamente sincronizzato con il Primary.
Si sta eseguendo un'inserzione (insert) in «Prymer» con un certo valore temporale. Questo inserimento aumenta il contatore interno di 11, se è il massimo. Oppure controllerà i valori delle ore e si sincronizzerà in base alle ore, se i valori delle ore sono superiori. Questo consente di ordinare in base al tempo.
Dopo che viene effettuata la registrazione, si verifica un momento importante. Le ore in «MongoDB» vengono incrementate solo in caso di registrazione nell'«Oplog». Questo è l'evento che cambia lo stato del sistema. In tutti gli articoli classici, un evento è considerato l'arrivo di un messaggio in un nodo: se un messaggio è arrivato, significa che il sistema ha cambiato il suo stato.
Questo è collegato al fatto che, durante l'analisi, non è sempre possibile comprendere come verrà interpretato questo messaggio. Sappiamo con certezza che se non è riflesso nell'«Oplog», non verrà interpretato in alcun modo, e l'unico cambiamento nello stato del sistema è la registrazione nell'«Oplog». Questo semplifica tutto: semplifica il modello, consente di mantenere un ordinamento all'interno di un singolo replica set e molte altre cose utili.
Viene restituito un valore già registrato in «OpLog» - sappiamo che in «OpLog» è già presente questo valore, e il suo timestamp è 12. Ora, diciamo che la lettura inizia da un altro nodo (Secondary), e lui passa già l'afterClusterTime nel messaggio. Dice: «Ho bisogno di tutto ciò che è successo almeno dopo le 12 o alle dodici» (vedi figura sopra).
Questo è ciò che si chiama Causal a consistent (CAT). C'è un concetto nella teoria secondo cui si tratta di un certo intervallo di tempo che è autonomamente consistente. In questo caso si può dire che si tratta di uno stato del sistema che è stato osservato al momento 12.
Attualmente qui non c'è nulla, perché simula una situazione in cui è necessario che il Secondario replichi i dati dal Primario. Sta aspettando... Ed ecco che i dati sono arrivati - restituisce indietro questi valori.

Ecco come funziona approssimativamente. Quasi.
Cosa significa "quasi"? Immaginiamo che ci sia una persona che ha letto e compreso come funziona tutto questo. Ha capito che ogni volta avviene il ClusterTime, aggiorna gli orologi logici interni e poi la registrazione successiva aumenta di uno. Questa funzione occupa 20 righe. Supponiamo che questa persona passi il numero intero a 64 bit più grande possibile, meno uno.
Perché "meno uno"? Perché gli orologi interni verranno impostati su questo valore (ovviamente, è il massimo possibile e più dell'attuale tempo), poi si registrerà nel "OpLog", e gli orologi verranno incrementati di nuovo di uno – e ci sarà già il valore massimo (lì ci sono solo tutti uno, non c'è spazio oltre, unsaint int).
È chiaro che dopo questo il sistema diventa completamente inaccessibile per qualsiasi cosa. Può essere solo scaricato, ripulito – molto lavoro manuale. Disponibilità completa:

Inoltre, se questo viene replicato altrove, semplicemente si blocca l'intero cluster. Una situazione assolutamente inaccettabile, che chiunque può organizzare molto rapidamente e facilmente! Perciò abbiamo considerato questo aspetto come uno dei più importanti. Come possiamo prevenirlo?
Il nostro obiettivo è firmare clusterTime
Così viene trasmesso nel messaggio (fino al testo blu). Ma abbiamo anche iniziato a generare una firma (testo blu):

La firma è generata da una chiave che viene conservata all'interno del database, all'interno di un perimetro sicuro; viene generata e aggiornata (questo non è visibile agli utenti). Viene generato un hash, e ogni messaggio viene firmato al momento della creazione e validato al momento della ricezione.
Probabilmente le persone si chiedono: «Quanto rallenta tutto questo?» Ho già detto che deve funzionare rapidamente, specialmente in assenza di questa funzionalità.
Cosa significa utilizzare la coerenza causale in questo caso? Si tratta di mostrare il parametro afterClusterTime. E senza di esso, trasmetterà semplicemente i valori comunque. Il gossiping, a partire dalla versione 3.6, funziona sempre.
Se lasciamo la generazione costante di firme, ciò rallenterà il sistema anche in assenza della funzionalità, il che non è in linea con i nostri approcci e requisiti. E cosa abbiamo fatto?
Fallo rapidamente!
Una cosa abbastanza semplice, ma il trucco è interessante – condividerò, magari a qualcuno interesserà.
Abbiamo un hash in cui vengono memorizzati i dati firmati. Tutti i dati passano attraverso la cache. La cache non firma specificamente il tempo, ma il Range. Quando viene ricevuto un certo valore, generiamo il Range, mascheriamo gli ultimi 16 bit e firmiamo quel valore:

Ricevendo una tale firma, acceleriamo il sistema (in modo ipotetico) di 65 mila volte. Funziona splendidamente: quando abbiamo condotto esperimenti, il tempo per un aggiornamento sequenziale si è effettivamente ridotto di 10 mila volte. È chiaro che quando sono disordinati, questo non funziona. Ma nella maggior parte dei casi pratici, funziona. La combinazione della firma Range insieme alla firma ha risolto il problema della sicurezza.
Cosa abbiamo imparato?
Le lezioni che ne abbiamo tratto:
- È importante leggere materiali, storie, articoli, perché ci sono molte cose interessanti. Quando lavoriamo su una funzione (soprattutto ora, quando abbiamo fatto delle transazioni, ecc.), bisogna leggere e approfondire. Questo richiede tempo, ma è davvero molto utile perché diventa chiaro dove ci troviamo. Non abbiamo inventato nulla di nuovo – abbiamo semplicemente preso ingredienti.
C'è una certa differenza di pensiero quando si tratta di una conferenza accademica (come ad esempio «Sigmon») – lì tutti si concentrano su nuove idee. Qual è la novità del nostro algoritmo? Qui non c'è nulla di particolarmente nuovo. La novità risiede piuttosto nel modo in cui abbiamo combinato approcci esistenti. Quindi, prima di tutto, è necessario leggere i classici, partendo da Lamport.
- In produzione ci sono requisiti completamente diversi. Sono sicuro che molti di voi non affrontano database 'sferici' in un vuoto astratto, ma cose normali e reali, che presentano problemi di disponibilità, latenza e resilienza agli errori.
- Infine, abbiamo dovuto considerare diverse idee e combinare insieme articoli anche molto diversi in un unico approccio. L'idea della firma, ad esempio, proviene da un articolo che trattava il protocollo Paxos, per i failover non byzantini all'interno di un protocollo di autorizzazione, e per quelli byzantini – al di fuori del protocollo di autorizzazione… In sostanza, è esattamente quello che abbiamo realizzato.
Non c'è assolutamente nulla di nuovo qui! Ma una volta che mescoliamo tutto insieme... È come dire che la ricetta dell'insalata russa non conta, perché le uova, la maionese e i cetrioli erano già stati inventati... È più o meno la stessa storia.

Con questo concludo. Grazie!
Domande
Domanda dal pubblico (d'ora in poi - D): – Grazie, Michail, per la presentazione! L'argomento del tempo è interessante. Utilizzate il Gossiping. Avete detto che tutti hanno il proprio tempo, tutti conoscono il proprio orario locale. Ho capito che abbiamo un driver: ci possono essere molti clienti con i loro driver, anche molti query-planner, molti shard... A cosa porta il sistema se dovesse emergere una discrepanza: qualcuno decide che è un minuto avanti, qualcun altro è un minuto indietro? Dove ci ritroveremo?
MT: – Ottima domanda in realtà! Volevo proprio parlare degli shard. Se ho capito bene la domanda, abbiamo questa situazione: c'è lo shard 1 e lo shard 2, la lettura avviene da questi due shard - ci sono delle discrepanze, non interagiscono perché il tempo che conoscono è diverso, specialmente il tempo che esiste nei loro log.
Supponiamo che il shard 1 abbia registrato un milione di record, mentre il shard 2 non ne abbia registrato nessuno, e una richiesta sia arrivata a entrambi i shard. Se il primo shard ha un afterClusterTime superiore a un milione, in questa situazione, come ho spiegato, il shard 2 non risponderà mai.
D: – Volevo sapere come si sincronizzano e scelgono un orario logico?
MT: – È molto semplice sincronizzarsi. Quando a un shard arriva un afterClusterTime e non trova un'ora nel "Op-log", inizia un no approved. In altre parole, imposta manualmente il suo tempo a quel valore. Ciò significa che non ha eventi che rispondono a quella richiesta. Crea questo evento artificialmente e diventa quindi Causal Consistent.
D: – E se dopo arrivassero ancora eventi che sono stati persi nella rete?
MT: – I shard sono progettati in modo tale che non arriveranno, poiché si tratta di un single master. Se ha già registrato, non arriveranno più, ma solo in seguito. Non può succedere che qualcosa si sia bloccato, poi farà un no write e successivamente quegli eventi arriveranno, infrangendo così la Causal consistency. Quando fa un no write, tutti devono arrivare dopo (li attenderà).

D: – Ho alcune domande riguardo alle code. La causal consistency implica che esista una certa sequenza di azioni da eseguire. Cosa succede se un pacchetto va perso? Ecco, va il 10°, l'11°… il 12° è scomparso, e tutti gli altri stanno aspettando che venga eseguito. E improvvisamente la macchina si guasta, non possiamo fare nulla. C'è una lunghezza massima della coda che si accumula prima che venga eseguita? Quale errore fatale si verifica nella perdita di uno stato qualsiasi? Inoltre, se registriamo che c'è uno stato precedente, da esso dobbiamo in qualche modo partire? E da esso non siamo partiti!
MT: – Anche questa è un'ottima domanda! Cosa facciamo? In MongoDB esiste il concetto di scritture in quorum e letture in quorum. In quali circostanze un messaggio potrebbe andare perso? Quando la scrittura non è in quorum o quando la lettura non è in quorum (potrebbe anche rimanere qualche spazzatura).
Riguardo alla causal consistency, abbiamo effettuato un'ampia verifica sperimentale, i cui risultati hanno mostrato che quando le scritture e le letture non sono in quorum, si verificano violazioni della causal consistency. Esattamente ciò che stai dicendo!
Il nostro consiglio: utilizzare almeno la lettura a quorum quando si utilizza la consistenza causale. In questo caso, non verrà perso nulla, anche se la scrittura a quorum fallisce... Questa è una situazione ortogonale: se un utente non vuole perdere dati, deve usare la scrittura a quorum. La consistenza causale non garantisce durabilità. La garanzia di durabilità è fornita dalla replica e dalle macchine associate alla replica.
D: – Quando creiamo un'istanza che esegue lo sharding (non master, ma slave rispettivamente), si basa sul tempo unix della propria macchina o sul tempo del "master"; si sincronizza la prima volta o periodicamente?
MT: – Ora chiarirò. Uno shard (cioè una partizione orizzontale) ha sempre un Primary. In uno shard può esserci un "master" e ci possono essere repliche. Ma lo shard supporta sempre la scrittura, perché deve mantenere un certo dominio (in uno shard c'è un Primary).
D: – Quindi tutto dipende esclusivamente dal "master"? Viene sempre usato il tempo del "master"?
MT: – Sì. Si potrebbe dire metaforicamente: gli orologi ticchettano quando avviene una scrittura nel "master", nell'"Op-log".
D: – Abbiamo un cliente che si connette e non ha bisogno di sapere nulla riguardo al tempo?
MT: – Non è necessario sapere nulla! Parlando di come funziona per il cliente: il cliente, quando desidera utilizzare la Causal consistency, deve aprire una sessione. Adesso ci sono tutto: sia le transazioni nella sessione, sia il retrieve a rights… La sessione è un ordinamento di eventi logici che avvengono con il cliente.
Se apre questa sessione e afferma di volere la Causal consistency (se per impostazione predefinita la sessione supporta la Causal consistency), tutto funziona automaticamente. Il driver memorizza questo tempo e lo aumenta quando riceve un nuovo messaggio. Ricorda quale risposta ha restituito il server che ha fornito i dati. La richiesta successiva conterrà afterCluster («tempo superiore a questo»).
Il cliente non deve sapere davvero nulla! È completamente opaco per lui. Se le persone utilizzano queste funzionalità, cosa potrebbe permettere? Prima di tutto, è possibile leggere in modo sicuro dai secondari: si può scrivere sul Primary e leggere da secondari replicati geograficamente, ed essere certi che funzioni. In questo modo, le sessioni registrate sul Primary possono essere trasferite anche ai Secondary, cioè si possono utilizzare più sessioni invece di una sola.
D: – È strettamente correlata al tema della coerenza eventuale una nuova branca della scienza computazionale: i tipi di dati CRDT (Conflict-free Replicated Data Types). Avete considerato l'integrazione di questi tipi di dati nel database e cosa ne pensate?
MT: – Ottima domanda! CRDT ha senso per i conflitti durante la scrittura: in MongoDB – single master.
D: – Ho una domanda dagli sviluppatori DevOps. Nel mondo reale ci sono situazioni subdole, in cui si verifica un fallimento bizantino, e persone malintenzionate all'interno di un perimetro protetto iniziano a interferire con il protocollo, inviando pacchetti craftati in modo speciale?

MT: – Le persone malintenzionate all'interno del perimetro sono come un cavallo di Troia! Possono fare molte cose cattive.
D: – È evidente che lasciare nel server, per così dire, un varco attraverso cui può passare uno zoo di elefanti, può andare a compromettere l'intero cluster per sempre... Ci vorrà tempo per un ripristino manuale... Questo, a dir poco, è inaccettabile. D'altra parte, è curioso sapere: nella vita reale, nella pratica, si verificano situazioni in cui attacchi interni di questo tipo accadono realmente?
MT: – Poiché non mi capita spesso di affrontare violazioni della sicurezza nella vita reale, non posso dire se accadano. Ma parlando della filosofia di sviluppo, riteniamo questo: abbiamo un perimetro che garantisce a coloro che si occupano della sicurezza – è un lucchetto, un muro; e all'interno del perimetro si può fare tutto ciò che si vuole. È chiaro che ci sono utenti con la possibilità solo di visualizzare e altri che possono eliminare il catalogo.
A seconda dei privilegi, il danno che gli utenti possono infliggere può variare da un clic del mouse a una distruzione totale. È chiaro che un utente con pieni diritti può fare praticamente qualsiasi cosa. Un utente con diritti limitati può causare un danno significativamente inferiore. In particolare, non può rompere il sistema.
D: – All'interno di un perimetro sicuro, ci sono persone che creano protocolli inaspettati per il server, per metterlo sotto stress, e se va bene, anche l'intero cluster... È mai così "buono"?
MT: – Non ho mai sentito parlare di queste cose. Che sia possibile sovraccaricare un server in questo modo non è un segreto. Sovraccaricarlo dall'interno, essendo un utente autorizzato che può registrare un messaggio... In realtà non è possibile, perché comunque ci sarà una verifica. C'è la possibilità di disattivare questa autenticazione per quegli utenti che non la vogliono – sono affari loro; in un certo senso, hanno abbattuto le mura e possono farci entrare un elefante che schiaccerà... In effetti, si può vestire da tecnico, venire e rimuovere!
D: – Grazie per la relazione. Sergey ("Yandex"). In "Mongo" c'è una costante che limita il numero di membri votanti nel Replica Set, e questa costante è 7 (sette). Perché è una costante? Perché non è un parametro qualsiasi?
MT: – Nel Replica Set abbiamo anche 40 nodi. C'è sempre una maggioranza. Non so quale versione...
D: – Nel Replica Set si possono avviare membri non votanti, ma per i votanti il massimo è 7. Come ci si comporta in questo caso in caso di spegnimento, se il nostro Replica Set è distribuito su 3 data center? Un data center può facilmente spegnersi e un'altra macchina può andare offline.
MT: – Questo è già un po' oltre il rapporto. È una questione generale. Forse posso parlarne più tardi.


Un po' di pubblicità 🙂
Grazie per essere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci ai tuoi conoscenti, , un'alternativa unica ai server entry-level, che abbiamo creato per te: (disponibili opzioni con RAID1 e RAID10, fino a 24 core e fino a 40GB DDR4).
Dell R730xd a metà prezzo nel data center Equinix Tier IV ad Amsterdam? Solo da noi nei Paesi Bassi! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — a partire da $99! Scopri di più su
Fonte: habr.com
