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

A volte, i requisiti pratici entrano in conflitto con la teoria, dove non sono considerati aspetti importanti per un prodotto commerciale. In questa relazione viene presentato il processo di selezione e combinazione di diversi approcci per la creazione di componenti di Causal consistency basati su ricerche accademiche, in base ai requisiti di un prodotto commerciale. Gli ascoltatori apprenderanno gli approcci teorici esistenti riguardanti orologi logici, tracciamento delle dipendenze, sicurezza dei sistemi, sincronizzazione degli orologi, e perché MongoDB ha scelto determinate soluzioni.
Mikhail Tyulenev (di seguito – MT): – Parlerò di Causal consistency – una caratteristica su cui abbiamo lavorato in MongoDB. Lavoro nel gruppo dei sistemi distribuiti, l'abbiamo realizzata circa due anni fa.

Nel processo, è stato necessario familiarizzare con un gran numero di ricerche accademiche, poiché questa caratteristica è ben studiata. Si è scoperto che nessun articolo soddisfa i requisiti di produzione di un database a causa delle esigenze piuttosto specifiche che possono esistere in qualsiasi applicazione di produzione.
Parlerò di come, come consumatori della ricerca accademica, trasformiamo queste informazioni in qualcosa che possiamo poi presentare ai nostri utenti come un piatto pronto, facile e sicuro da usare.
Causal consistency. Definiamo i concetti
Per iniziare, voglio dire a grandi linee cos'è la Causal consistency. Ci sono due personaggi – Leonard e Penny (serie «The Big Bang Theory»):

Supponiamo che Penny sia in Europa, e Leonard voglia farle una sorpresa, una festa. E non trova idea migliore che rimuoverla dalla lista amici, inviando a tutti i suoi amici un aggiornamento sul feed: «Facciamo una sorpresa a Penny!» (lei è in Europa, mentre dorme, non vede nulla e non può vederlo, perché non è lì). Alla fine, elimina questo post, cancella dal «Feed» e ripristina l'accesso, affinché lei non se ne accorga e non ci siano scandali.
Tutto ciò è fantastico, ma supponiamo che il sistema sia distribuito e che gli eventi non siano andati come previsto. Può accadere, ad esempio, che il vincolo di accesso di Penny si sia verificato dopo che questo post è apparso, se gli eventi non sono correlati 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 non banali dei database – molto pochi le supportano. Passiamo ai modelli.
Modelli di Coerenza (Consistency Models)
Che cos'è in realtà un modello di coerenza nei database? Sono alcune garanzie che un sistema distribuito fornisce riguardo a quali dati e in quale sequenza il cliente può ricevere.
In linea di principio, tutti i modelli di coerenza si riducono a quanto un sistema distribuito assomigli a un sistema che funziona, ad esempio, su un solo nodo su un laptop. E quanto un sistema che funziona su migliaia di nodi geograficamente distribuiti assomiglia a un laptop, in cui tutte queste proprietà vengono eseguite fondamentalmente in modo automatico.
Pertanto, i modelli di coerenza si applicano solo ai sistemi distribuiti. Tutti i sistemi che esistevano in precedenza e funzionavano su un'unica scalabilità verticale non aveva tali problemi. C'era un solo Buffer Cache, e da esso veniva sempre letto tutto.
Modello Strong
La prima vera modello è lo Strong (o la linea di rise ability, come viene spesso chiamato). Questo è un modello di coerenza che garantisce che ogni modifica, non appena si riceve conferma che è avvenuta, diventi 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, ed è anche molto costosa. Tuttavia, è ben supportata. È semplicemente molto costosa e lenta – viene utilizzata raramente. Questo si chiama rise ability.
C'è anche un'altra proprietà, più robusta, supportata in 'Spanner' – chiamata Coerenza Esterna. Ne parleremo più avanti.
Causale
Il seguente è Causal, proprio ciò di cui parlavo. Tra Strong e Causal ci sono ancora alcuni sottolivelli, di cui non parlerò, ma tutti si riducono a Causal. È un modello importante perché è il più forte di tutti i modelli, la coerenza più forte in presenza di reti o partizioni.
I Causals sono la situazione in cui gli eventi sono legati da una relazione causale. Spesso vengono percepiti come Read your own rights dal punto di vista del cliente. Se un cliente ha osservato alcuni valori, non può vedere i valori che erano nel passato. Inizia già a vedere letture prefissate. Questo si riduce a una questione comune.
I Causals come modello di coerenza rappresentano un ordinamento parziale degli eventi sul server, in cui gli eventi da tutti i clienti sono osservati nella stessa sequenza. In questo caso: Leonard e Penny.
Eventual
Il terzo modello è la Eventual Consistency. Questo è ciò che supporta assolutamente tutti i sistemi distribuiti, il modello minimo che ha senso. Significa quanto segue: quando ci sono alcuni cambiamenti nei dati, a un certo punto diventano coerenti.
In quel momento non dice nulla, altrimenti si trasformerebbe in External Consistency – sarebbe completamente un'altra storia. Tuttavia, è un modello molto popolare, il più comune. Per impostazione predefinita, tutti gli utenti dei sistemi distribuiti utilizzano proprio la Eventual Consistency.
Voglio fornire alcuni esempi comparativi:

Cosa significano queste frecce?
- Latenza. Con l'aumentare della forza della coerenza, diventa maggiore per motivi evidenti: è necessario effettuare più registrazioni, ottenere conferma da tutti gli host e nodi coinvolti nel cluster che i dati sono già presenti. Pertanto, nella Eventual Consistency la risposta più rapida è la più comune, perché lì di solito è possibile anche commettere in memoria e questo è fondamentalmente sufficiente.
- Disponibilità. Se si considera come possibilità del sistema di rispondere in caso di guasti di rete, partizioni o eventuali malfunzionamenti – la tolleranza ai guasti aumenta con la diminuzione del modello di coerenza, poiché è sufficiente che un host sia attivo e fornisca alcuni dati. La Eventual Consistency non garantisce nulla riguardo ai dati – potrebbero essere qualsiasi cosa.
- Anomalie. In questo caso, ovviamente, aumenta il numero di anomalie. In Strong Consistency non dovrebbero praticamente essercene, mentre in Eventual Consistency possono esserci tutti i tipi di anomalie. La domanda è: perché le persone scelgono Eventual Consistency se contiene anomalie? La risposta è che i modelli di Eventual Consistency sono applicabili, e le anomalie esistono, ad esempio, solo per brevi periodi di tempo; c'è la possibilità di utilizzare un master per la lettura e di ottenere dati più o meno consistenti; spesso è possibile utilizzare modelli di consistenza forte. In pratica funziona, e spesso il numero di anomalie è limitato nel tempo.
Teorema CAP
Quando vedi le parole consistency, availability – cosa ti viene in mente? Giusto – il teorema CAP! Ora voglio sfatare il mito… Non sono io – è Martin Kleppmann, che ha scritto un ottimo articolo, un ottimo libro.

Il teorema CAP è un principio formulato negli anni 2000, riguardante Consistency, Availability, Partitions: prendi due a caso, e non puoi scegliere tre. Era un certo principio. È stato dimostrato come teorema alcuni anni dopo, da Gilbert e Lynch. Poi è stato utilizzato come mantra – i sistemi sono stati classificati in CA, CP, AP e così via.
Questo teorema è stato in realtà dimostrato per i seguenti casi… In primo luogo, Availability è stata considerata non come un valore continuo da zero a cento (0 – sistema "morto", 100 – risponde rapidamente; ci siamo abituati a vederla in questo modo), ma come una proprietà dell'algoritmo che garantisce che per tutte le sue esecuzioni restituisca dati.
Non c'è nemmeno una parola sul tempo di risposta! Esiste un algoritmo che restituisce dati dopo 100 anni – un algoritmo available assolutamente magnifico, che fa parte del teorema CAP.
In secondo luogo: il teorema è stato dimostrato per le modifiche nei valori della stessa chiave, pur essendo queste modifiche una linea resizable. Ciò significa che in realtà non vengono praticamente utilizzate, perché ci sono altri modelli di Eventual Consistency, Strong Consistency (forse).
A cosa serve tutto questo? Al fatto che il teorema CAP, proprio nella forma in cui è stato dimostrato, è praticamente inapplicabile, usato raramente. Nella forma teorica limita in qualche modo tutto. Risulta un certo principio, che è intuitivamente corretto, ma che non è in alcun modo dimostrato.
Causal consistency è il modello più forte
Quello che sta succedendo ora - si possono ottenere tutte e tre le cose: Coerenza, Disponibilità può essere raggiunta attraverso le Partizioni. In particolare, la coerenza causale è il modello di coerenza più forte, che funziona anche in presenza di Partizioni (interruzioni nella rete). Ecco perché suscita un grande interesse e per questo ci siamo dedicati.

In primo luogo, semplifica il lavoro degli sviluppatori di applicazioni. In particolare, c'è un ampio supporto da parte del server: quando tutte le registrazioni che avvengono all'interno di un cliente arrivano garantite in tale sequenza su un altro cliente. In secondo luogo, resiste alle partizioni.
La cucina interna di MongoDB
Ricordando che è l'ora del pranzo, ci spostiamo in cucina. Vi parlerò del modello del 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, cioè lo sharding; e all'interno di ogni shard supporta anche la ridondanza dei dati, cioè la replica.
Lo sharding in "MongoDB" (un DB non relazionale) esegue un bilanciamento automatico, cioè ogni raccolta di documenti (o "tabella" in termini di dati relazionali) viene suddivisa in parti, e il server sposta automaticamente queste parti tra gli shard.
Il Query Router, che distribuisce le richieste, è per il cliente un certo tipo di client, attraverso il quale lavora. Sa già dove si trovano quali dati e dirige tutte le richieste al giusto shard.
Un'altra cosa importante: MongoDB è un single master. C'è un Primary - può gestire le registrazioni che supportano le chiavi che contiene. Non è possibile effettuare scritture multi-master.
Abbiamo rilasciato la versione 4.2 - sono emerse nuove cose interessanti. In particolare, abbiamo inserito Lucene - ricerca - ovvero java eseguibile direttamente in "Mongo", ed è diventato possibile eseguire ricerche attraverso Lucene, proprio come in "Elasticsearch".
E abbiamo creato un nuovo prodotto - Charts, che è anche disponibile su "Atlas" (il cloud di Mongo). Hanno un Free Tier - puoi sperimentare con questo. Mi è piaciuto molto Charts - visualizzazione dei dati, molto intuitiva.
Ingredienti della coerenza causale
Ho contato circa 230 articoli pubblicati su questo tema - da Leslie Lampert. Ora vi trasmetterò dalla mia memoria alcune parti di questi materiali.

Tutto è cominciato con un articolo di Leslie Lampert, scritto negli anni '70. Come potete vedere, continuano a esserci ricerche in questo campo. Attualmente, la consistenza causale sta suscitando interesse a causa dello sviluppo delle sistemi distribuiti.
Limitazioni
Quali sono i limiti? Questo è in realtà uno dei punti principali, perché i vincoli imposti dai sistemi di produzione sono molto diversi da quelli che esistono negli articoli accademici. Spesso sono piuttosto artificiali.

- Innanzitutto, "MongoDB" è un master singolo, come ho già detto (questo semplifica molto).
- Riteniamo che il sistema debba supportare circa 10.000 shard. Non possiamo prendere decisioni architetturali che limiterebbero esplicitamente questo valore.
- Abbiamo un cloud, ma riteniamo che una persona debba avere la possibilità di scaricare un binary, eseguirlo sul proprio laptop e tutto funzioni perfettamente.
- Riteniamo che nel Research venga raramente utilizzato: i clienti esterni possono fare qualsiasi cosa. "MongoDB" è open source. Di conseguenza, i clienti possono essere così intelligenti e maliziosi da volere rompere tutto. Prevediamo che possano verificarsi fasi di Byzantine.
- Per i clienti esterni, che si trovano al di fuori del perimetro, c'è un'importante limitazione: se questa funzionalità è disattivata, non ci dovrebbero essere degradi nelle performance.
- Un altro punto, totalmente anti-accademico: la compatibilità tra versioni precedenti e future. I driver vecchi devono supportare i nuovi aggiornamenti, e il database deve supportare i vecchi driver.
In generale, tutto ciò impone delle limitazioni.
Componenti della consistenza causale
Ora parlerò di alcuni componenti. Se si considera la consistenza causale, si possono evidenziare vari blocchi. Abbiamo scelto dai lavori che appartengono a un certo blocco: tracciamento delle dipendenze, scelta degli orologi, come sincronizzare questi orologi tra loro e come garantiamo la sicurezza – questo è un piano approssimativo di ciò di cui parlerò:

Tracciamento completo delle dipendenze (Full Dependency Tracking)
A cosa serve? Per garantire che, quando i dati vengono replicati, ogni record e ogni modifica ai dati contenga informazioni su quali modifiche dipende. La prima e più semplice modifica è quando ogni messaggio che contiene un record include informazioni sui messaggi precedenti:

In questo esempio, il numero tra parentesi graffe rappresenta i numeri dei record. A volte questi record con i valori vengono trasferiti anche per intero, in altre occasioni vengono trasferite versioni specifiche. La sostanza sta nel fatto che ogni modifica contiene informazioni sulla precedente (questo è ciò che normalmente trasporta).
Perché abbiamo deciso di non utilizzare questo approccio (tracciamento completo)? È ovvio che questo approccio non è pratico: ogni modifica in un social network dipende da tutte le modifiche precedenti in quel social network, trasmettendo, ad esempio, ‘Facebook’ o ‘Vkontakte’ in ogni aggiornamento. Tuttavia, ci sono molte ricerche sul Full Dependency Tracking: queste sono reti pre-sociali, per alcune situazioni funziona davvero.
Tracciamento delle dipendenze esplicite (Explicit Dependency Tracking)
Il successivo è più limitato. Qui viene considerata solo la trasmissione delle informazioni che dipendono esplicitamente. Ciò da cui dipende, di solito, è già definito dall'Applicazione. Quando i dati vengono replicati, durante la richiesta vengono forniti solo risposte quando le dipendenze precedenti sono state soddisfatte, cioè mostrate. Questo è il cuore di come funziona la coerenza causale.

Vede che il record 5 dipende dai record 1, 2, 3, 4; di conseguenza, aspetta che il cliente acceda alle modifiche apportate dall'accesso di Penny, quando tutte le modifiche precedenti sono già passate nel database.
Questo non ci soddisfa nemmeno, poiché ci sarebbe comunque troppa informazione, il che rallenterebbe. Esiste un altro approccio...
Orologi di Lamport (Lamport Clock)
Sono molto vecchi. Lamport Clock implica che queste dipendenze si riducono a una funzione scalare, che è chiamata Lamport Clock.
La funzione scalare è un certo numero astratto. Spesso viene chiamato tempo logico. Ad ogni evento, questo counter aumenta. Il counter attualmente conosciuto dal processo invia ogni messaggio. È chiaro che i processi possono essere disallineati e avere tempi completamente diversi. Tuttavia, attraverso questo scambio di messaggi, il sistema somehow bilancia 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 altro pezzo di questa collezione. È chiaro come possano non andare in coda? Prima il Feed dirà: "Replicato", 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, allora ci troveremo esattamente nella situazione di cui ho parlato.
Vedete come aumenta logicalmente il tempo del counter sul Feed:

Pertanto, la principale caratteristica di questo Lamport Clock e della coerenza causale (spiegata tramite Lamport Clock) è la seguente: se abbiamo eventi A e B, e l'evento B dipende dall'evento A *, ne deriva che il LogicalTime dell'Evento A è minore del LogicalTime dell'Evento B.
* A volte si dice anche che A è accaduto prima di B, il che significa che A è successo prima di B: è una relazione che ordina parzialmente l'intero insieme di eventi che si sono verificati.
Il contrario non è vero. Questo è in effetti uno dei principali svantaggi del Lamport Clock: l'ordine parziale. Esiste il concetto di eventi simultanei, cioè eventi nei quali né (A è successo prima di B), né (A è successo dopo B). Un esempio può essere l'aggiunta simultanea di qualcuno come amico da parte di Leonardo (non necessariamente da Leonardo stesso, ma ad esempio da Sheldon).
Questo è il principio che viene spesso utilizzato quando si lavora con i Lamport Clock: si guarda precisamente alla funzione e da questo si trae una conclusione: forse questi eventi sono dipendenti. Perché in un senso è vero: se LogicalTime A è minore di LogicalTime B, allora B non può essere successo prima di A; mentre se è maggiore, potrebbe.
Orologi vettoriali (Vector Clock)
Lo sviluppo logico degli orologi di Lamport sono gli orologi vettoriali. Si differenziano in quanto ogni nodo qui contiene i propri orologi separati, che vengono trasmessi come un vettore.
In questo caso, vedete che l'indice zero del vettore corrisponde al Feed, mentre il primo indice del vettore si riferisce ai Friends (ognuno di questi nodi). E ora stanno per aumentare: l'indice zero del "Feed" aumenta durante la scrittura – 1, 2, 3:

Perché gli Orologi Vettoriali sono migliori? Perché consentono di capire quali eventi sono simultanei e quando si verificano su diversi nodi. Questo è molto importante per il sistema di shard, come "MongoDB". Tuttavia, non abbiamo optato per questo, anche se è una cosa magnifico, funziona meravigliosamente e probabilmente ci sarebbe stata utile...
Se abbiamo 10 mila shard, non possiamo trasmettere 10 mila componenti, anche se comprimiamo, pensiamo a qualche altra soluzione – la payload utile sarà comunque di gran lunga inferiore al volume totale di questo vettore. Pertanto, con grande riluttanza, abbiamo abbandonato questo approccio e siamo passati a un altro.
Spanner TrueTime. Orologi atomici
Ho detto che parlerò di "Spanner". È una cosa fantastica, proprio del XXI secolo: orologi atomici, sincronizzazione GPS.
Qual è l'idea? "Spanner" è un sistema di Google, che recentemente è persino diventato accessibile alle persone (vi hanno aggiunto SQL). Ogni transazione ha un certo timestamp. Poiché il tempo è sincronizzato*, a ogni evento può essere assegnato un determinato orario: gli orologi atomici hanno un tempo di attesa, dopo il quale avviene garantito un altro orario.

In questo modo, semplicemente registrando nel DB e aspettando un certo periodo di tempo, si garantisce automaticamente la Serializability dell'evento. Hanno il modello di Consistency più forte che si possa immaginare – si tratta di External Consistency.
* Questo è il principale problema degli orologi di Lamport – non sono mai sincronizzati nei sistemi distribuiti. Possono divergere, anche in presenza di NTP funzionano comunque non molto bene. "Spanner" ha orologi atomici e sincronizzazione, sembra, nell'ordine dei microsecondi.
Perché non l'abbiamo scelto? Non presupponiamo che i nostri utenti abbiano orologi atomici integrati. Quando appariranno, essendo integrati in ogni laptop, ci sarà una sorta di super-sincronizzazione GPS – allora sì... Ma per ora, la cosa migliore che sia possibile – è "Amazon", le Stazioni Base – per i fanatici... Pertanto, abbiamo utilizzato altri orologi.
Orologi Ibridi (Hybrid Clock)
Questo è praticamente ciò che scatta in «MongoDB» assicurando la coerenza causale. In cosa consistono gli ibridi? Un ibrido è un valore scalare, ma è composto da due componenti:

- Il primo è l'epoca unix (quanti secondi sono passati dall'inizio del "mondo informatico").
- Il secondo è un certo incremento, anch'esso un intero unsigned a 32 bit.
Questo è sostanzialmente tutto. Esiste un approccio in cui la parte che si occupa del tempo è sempre sincronizzata con l'orologio; ogni volta che avviene un aggiornamento, questa parte si sincronizza con l'orologio e si scopre che il tempo è sempre più o meno corretto, mentre l'incremento consente di distinguere gli eventi che sono accaduti nello stesso momento.
Perché è importante per «MongoDB»? Perché consente di effettuare backup e ripristini a un determinato momento, cioè gli eventi vengono indicizzati nel tempo. Questo è importante quando sono necessari alcuni eventi; per il database, gli eventi sono quelle modifiche nel DB che si sono verificate in determinati intervalli di tempo.
Vi dirò il motivo principale solo a voi (per favore, non ditelo a nessuno)! Lo abbiamo fatto perché così appaiono i dati ordinati e indicizzati nel MongoDB OpLog. L'OpLog è una struttura dati che contiene tutte le modifiche nel database: inizialmente entrano nell'OpLog e solo successivamente vengono applicate al proprio Storage nel caso in cui si tratti di dati replicati o shard.
Questa è stata la motivazione principale. Ci sono comunque anche requisiti pratici per lo sviluppo del database, il che significa che deve essere semplice: poco codice, il minor numero possibile di cose rotte da riscrivere e testare. Il fatto che i nostri oplog siano stati indicizzati con orologi ibridi ha aiutato notevolmente e ha permesso di compiere scelte corrette. Questo si è rivelato efficace e in qualche modo ha funzionato magicamente, nel primo prototipo. È stato davvero fantastico!
Sincronizzazione degli orologi
Esistono diversi modi di sincronizzazione descritti nella letteratura scientifica. Parlo di sincronizzazione quando abbiamo due shard diversi. Se c'è un replica set – non è necessaria alcuna sincronizzazione: è un "single-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, qui la sincronizzazione del tempo è importante. Qui gli orologi vettoriali sono stati di maggiore aiuto! Ma noi non li abbiamo.

Il secondo modo è rappresentato dai "Heartbeats". È possibile scambiarsi alcuni segnali che avvengono ogni unità di tempo. Ma i "Heartbeats" sono troppo lenti, non possiamo garantire la latenza ai nostri clienti.
Il tempo vero – è sicuramente una cosa splendida. Ma, di nuovo, è probabilmente il futuro… Anche se in "Atlas" è già possibile farlo, ci sono già rapidi sincronizzatori di tempo "amazoniani". Ma questo non sarà disponibile per tutti.
Gossiping – è quando tutti i messaggi includono il tempo. È grosso modo quello che utilizziamo. Ogni messaggio tra nodi, driver, router di data node, assolutamente tutto per "MongoDB" – sono elementi, componenti del database che contengono orologi che scorrono. Ovunque c'è un valore di tempo ibrido, viene trasmesso. 64 bit? Questo è possibile.
Come funziona tutto questo insieme?
Qui considero un replica set per semplificare un po'. C'è un Primary e un Secondary. Il Secondary esegue la replicazione e non è sempre completamente sincronizzato con il Primary.
Avviene un inserimento nel "Primary" con un certo valore di tempo. Questo inserimento aumenta il contatore interno di 11, se è il massimo. Oppure controllerà i valori degli orologi e si sincronizzerà in base agli orologi, se i valori sono superiori. Questo consente di ordinare per tempo.
Dopo che viene effettuata la registrazione, avviene un momento importante. Gli orologi in "MongoDB" vengono incrementati solo in caso di registrazione nell'"OpLog". Questo è l'evento che modifica lo stato del sistema. In tutti gli articoli classici, un evento si considera l'arrivo di un messaggio nel nodo: se il messaggio è arrivato, significa che il sistema ha cambiato il proprio stato.
Questo è dovuto al fatto che durante la ricerca non è possibile capire completamente come questo messaggio verrà interpretato. Sappiamo con certezza che se non è riflesso nell'«OpLog», non verrà interpretato in alcun modo, e il cambiamento di stato del sistema è solo la registrazione nell'«OpLog». Questo ci semplifica tutto: sia il modello è semplificato, sia consente di gestire l'ordinamento all'interno di un unico replica-set, e molte altre cose utili.
Viene restituito il valore che è già registrato nell'«OpLog» – sappiamo che in «OpLog» c'è già questo valore, e il suo tempo è – 12. Ora, supponiamo che inizi la lettura da un'altra nodo (Secondary), e lui trasmette già afterClusterTime nel messaggio stesso. Dice: «Ho bisogno di tutto ciò che è accaduto almeno dopo le 12 o durante le dodici» (vedi l'immagine sopra).
Questo è ciò che viene chiamato Causal a consistent (CAT). Esiste un concetto nella teoria che rappresenta un certo intervallo di tempo, che è coerente in sé. In questo caso si può dire che questo è lo stato del sistema che è stato osservato al momento 12.
Al momento qui non c'è nulla, perché questo simula una situazione in cui è necessario che il nodo Secondario replichi i dati dal Primario. Sta aspettando... Ecco che i dati sono arrivati – restituisce questi valori.

Così funziona più o meno tutto. Quasi.
Cosa significa «quasi»? Supponiamo che ci sia una persona che ha letto e capito come funziona tutto. Ha capito che ogni volta si verifica ClusterTime, aggiorna gli orologi logici interni, e poi la successiva registrazione incrementa di uno. Questa funzione occupa 20 righe. Supponiamo che questa persona trasmetta il numero intero massimo a 64 bit, meno uno.
Perché «meno uno»? Perché gli orologi interni verranno inseriti in questo valore (ovviamente, si tratta del massimo possibile e maggiore del tempo attuale), poi ci sarà la registrazione nell'«OpLog», e gli orologi saranno incrementati di un'unità in più – e avremo già il valore massimo (ci saranno solo tutti gli uni, non c'è oltre, unsaint int).
È chiaro che dopo questo il sistema diventa assolutamente inaccessibile a qualsiasi cosa. Può essere solo scaricato, pulito – molto lavoro manuale. Disponibilità completa:

Inoltre, se questo si replica da qualche altra parte, tutto il cluster si bloccano. Una situazione assolutamente inaccettabile che chiunque può organizzare in modo molto rapido e semplice! Perciò, abbiamo considerato questo aspetto come uno dei più importanti. Come prevenirlo?
Il nostro approccio è firmare clusterTime
Così viene trasmesso nel messaggio (fino al testo blu). Ma abbiamo anche cominciato a generare una firma (testo blu):

La firma è generata da una chiave che è conservata all'interno del database, all'interno di un perimetro protetto; viene generata e aggiornata (gli utenti non vedono nulla di tutto questo). Viene generato un hash e ogni messaggio, al momento della creazione, è firmato e, al momento della ricezione, è validato.
Probabilmente sorge la domanda tra le persone: «Quanto rallenta tutto ciò?» Ho detto che deve funzionare rapidamente, specialmente in assenza di questa funzione.
Cosa significa utilizzare la coerenza causale in questo caso? Significa mostrare il parametro afterClusterTime. E senza questo, semplicemente passerà i valori in ogni caso. Gossiping, a partire dalla versione 3.6, funziona sempre.
Se lasciamo la generazione continua delle firme, questo rallenterà il sistema anche in assenza della funzione, il che non si allinea con i nostri approcci e requisiti. E cosa abbiamo fatto?
Falllo in fretta!
Una cosa abbastanza semplice, ma il trucco è interessante – lo condividerò, magari a qualcuno interesserà.
Abbiamo un hash che contiene dati firmati. Tutti i dati passano attraverso la cache. La cache non firma un tempo specifico, ma un Range. Quando arriva un certo valore, generiamo un Range, mascheriamo gli ultimi 16 bit, e questo valore lo firmiamo:

Ottenendo una tale firma, accelera il sistema (condizionalmente) di 65 mila volte. Funziona magnificamente: quando abbiamo condotto esperimenti – lì il tempo di aggiornamento sequenziale è realmente ridotto di 10 mila volte. È chiaro che quando sono disordinati, non è possibile. Ma nella maggior parte dei casi pratici funziona. La combinazione della firma Range insieme alla firma ha permesso di risolvere il problema della sicurezza.
Cosa abbiamo imparato?
Le lezioni che abbiamo tratto da questo:
- È necessario leggere materiali, storie, articoli, perché abbiamo molte cose interessanti da dire. Quando lavoriamo su una nuova funzionalità (soprattutto ora, quando stiamo facendo transazioni ecc.), bisogna leggere e capire. Questo richiede tempo, ma è davvero molto utile, perché diventa chiaro dove ci troviamo. Non abbiamo inventato nulla di nuovo, abbiamo semplicemente preso gli ingredienti.
In generale, si osserva una certa differenza nel modo di pensare quando si tiene una conferenza accademica (per esempio, «Sigmon») – lì tutti si concentrano su nuove idee. Qual è l'innovazione del nostro algoritmo? Qui non c'è nulla di particolarmente nuovo. L'innovazione risiede piuttosto nel modo in cui abbiamo combinato approcci esistenti. Quindi per prima cosa – bisogna leggere i classici, a partire 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 hanno problemi di disponibilità, latenza e tolleranza ai guasti.
- Infine, abbiamo dovuto considerare diverse idee e combinare insieme vari articoli completamente diversi in un unico approccio. L'idea della firma, per esempio, è venuta da un articolo che considerava il protocollo Paxos, utilizzato per i failover non bizantini all'interno di un protocollo di autorizzazione, e per quelli bizantini al di fuori di esso... In sostanza, è esattamente quello che abbiamo fatto.
Non c'è assolutamente nulla di nuovo qui! Ma non appena abbiamo mescolato tutto insieme... È come dire che la ricetta dell'insalata Olivier è sciocca, perché le uova, la maionese e i cetrioli sono già stati inventati... È più o meno la stessa storia.

Concludo qui. Grazie!
Domande
Domanda dal pubblico (da qui in poi – D): – Grazie, Mikhail, per la relazione! Il tema del tempo è interessante. Tu utilizzi Gossiping. Hai detto che ognuno ha il proprio tempo, tutti conoscono il proprio tempo locale. Ho capito che abbiamo un driver – ci possono essere molti clienti con driver, molti query-planner e anche diversi shard... Cosa succede al sistema se ci dovesse essere una discrepanza: qualcuno decide di essere un minuto avanti, qualcuno – un minuto indietro? Dove ci ritroveremo?
MT: – Ottima domanda, in effetti! Volevo proprio parlare dei shard. Se ho capito correttamente la domanda, la situazione è questa: ci sono lo shard 1 e lo shard 2, la lettura avviene da questi due shard – sono in disaccordo, non interagiscono tra loro, perché il tempo che conoscono è diverso, specialmente il tempo che hanno negli oplog.
Supponiamo che lo shard 1 abbia effettuato un milione di registrazioni, lo shard 2 – nessuna, e la richiesta sia arrivata a entrambi gli shard. E il primo ha un afterClusterTime superiore a un milione. In questa situazione, come ho spiegato, lo shard 2 non risponderà mai.
D: – Volevo sapere come si sincronizzano e scelgono un tempo logico unico?
MT: – Si sincronizzano molto semplicemente. Lo shard, quando riceve l'afterClusterTime e non trova il tempo nell'«Oplog», inizia un no approved. Cioè, alza manualmente il proprio tempo fino a quel valore. Questo significa che non ha eventi che corrispondono a questa richiesta. Crea quindi questo evento artificialmente e diventa così Causal Consistent.
D: – E se dopo ciò arrivano eventi che si sono persi nella rete?
MT: – Lo shard è strutturato in modo tale che non arriveranno più, poiché si tratta di un single master. Se ha già registrato, non arriveranno più, ma saranno successivi. Non può succedere che qualcosa si perda da qualche parte, poi faccia un no write e dopo che quegli eventi siano arrivati – e sia stata violata la Causal consistency. Quando fa un no write, tutti devono arrivare successivamente (lui li aspetterà).

D: – Ho alcune domande riguardo alle code. La Causal consistency implica che ci sia una certa coda di azioni da eseguire. Cosa succede se un pacchetto va perso? Ecco che è arrivato il 10°, 11°... il 12° è andato perso, e tutti gli altri aspettano che venga eseguito. E improvvisamente la macchina è morta, non possiamo fare nulla. C'è una lunghezza massima della coda che si accumula prima che venga eseguita? Qual è il fatal failure che si verifica con la perdita di uno qualsiasi stato? Soprattutto se registriamo che c'è uno stato precedente, da esso dobbiamo in qualche modo partire? Ma non ci si è partiti!
MT: – Anche questa è un'ottima domanda! Cosa facciamo? In MongoDB esiste il concetto di registrazioni di quorum, lettura di quorum. In quali casi un messaggio può andare perso? Quando la registrazione non è di quorum o quando la lettura non è di quorum (può anche arrivare qualche spazzatura).
Per quanto riguarda la coerenza causale, abbiamo effettuato un ampio esperimento che ha mostrato che, nel caso in cui le operazioni di scrittura e lettura siano non-quorum, si verificano violazioni della coerenza causale. Proprio quello che stai dicendo!
Il nostro consiglio: utilizzare almeno letture quorum quando si utilizza la coerenza causale. In questo caso, non andrà perso nulla, anche se la scrittura quorum dovesse andare persa... È una situazione ortogonale: se l'utente non vuole perdere dati, deve utilizzare scritture quorum. La coerenza causale non garantisce durabilità. La garanzia di durabilità è fornita dalla replicazione e dal meccanismo collegato alla replicazione.
D: – Quando creiamo un'istanza, che esegue lo sharding (non il master, ma lo slave corrispondentemente), si basa sul tempo UNIX della propria macchina o sul tempo del "master"; si sincronizza la prima volta o periodicamente?
MT: – Adesso chiarisco. Lo shard (cioè la partizione orizzontale) ha sempre un primario. All'interno dello shard può esserci un "master" e possono esserci repliche. Ma lo shard sostiene sempre la scrittura, perché deve mantenere un certo dominio (lo shard ha un Primario).
D: – Quindi tutto dipende esclusivamente dal "master"? Si usa sempre il tempo del "master"?
MT: – Sì. Si potrebbe dire in modo figurato: gli orologi ticchettano quando avviene una scrittura nel "master", nel "OpLog".
D: – Abbiamo un cliente che si connette, e non ha bisogno di sapere nulla riguardo al tempo?
MT: – Assolutamente non ha bisogno di sapere nulla! Parlando di come funziona lato cliente: quando il cliente desidera utilizzare la coerenza causale, deve aprire una sessione. Ora ci sono tutte: le transazioni nella sessione e l'ottenimento dei diritti... La sessione è un ordinamento di eventi logici che avvengono con il cliente.
Se apre questa sessione e dice che desidera la coerenza causale (se per impostazione predefinita la sessione supporta la coerenza causale), tutto funziona automaticamente. Il driver memorizza questo tempo e lo aumenta quando riceve un nuovo messaggio. Memorizza quale risposta ha restituito il server precedente, che ha restituito i dati. La richiesta successiva conterrà afterCluster ("tempo maggiore di questo").
Al cliente non serve sapere assolutamente nulla! È completamente opaco per lui. Se le persone utilizzano queste funzionalità, cosa consente di fare? Innanzitutto, si possono leggere in modo sicuro le secondarie: si può scrivere su Primary e leggere da secondarie replicate geograficamente, essendo certi che funzioni. Inoltre, le sessioni registrate su Primary possono essere trasferite anche su Secondary, ossia è possibile utilizzare più di una sessione.
D: – La tematica della Eventual consistency è strettamente legata a un nuovo elemento della scienza informatica: i tipi di dati CRDT (Conflict-free Replicated Data Types). Avete preso in considerazione l'integrazione di questi tipi di dati nel database e cosa ne pensate?
MT: – Ottima domanda! I CRDT hanno senso per i conflitti in scrittura: in MongoDB abbiamo un master unico.
D: – Ho una domanda dai DevOps. Nel mondo reale ci sono situazioni così ingannevoli quando si verifica un Failure bizantino, e persone malevole all'interno di un perimetro protetto iniziano a intromettersi nel protocollo, inviando pacchetti craftati in modo specifico?

MT: – Persone malevole all'interno del perimetro sono come un cavallo di Troia! Possono fare molte cose cattive.
D: – È chiaro che lasciare nel server, per così dire, un buco attraverso cui far passare un intero zoo di elefanti e abbattersi sull'intero cluster per sempre... Ci vorrebbe tempo per un ripristino manuale... Questo, per usare un eufemismo, è sbagliato. D'altra parte, è interessante sapere: nella vita reale, nella pratica, ci sono situazioni in cui tali attacchi interni si verificano realmente?
MT: – Poiché non mi capita spesso di affrontare brecce di sicurezza nella vita reale, non posso dire se effettivamente accadano. Ma se parliamo di filosofia dello sviluppo, noi riteniamo questo: abbiamo un perimetro che protegge i ragazzi che si occupano della sicurezza: è un lucchetto, un muro; e all'interno del perimetro si può fare qualsiasi cosa. È chiaro che ci sono utenti con la possibilità solo di visualizzare e vi sono utenti con la possibilità di cancellare un catalogo.
In base ai diritti, il danno che gli utenti possono infliggere può variare da un clic di mouse a un'azione da elefante. È chiaro che un utente con diritti completi può fare praticamente qualsiasi cosa. Un utente con diritti limitati può infliggere danni significativamente minori. In particolare, non può compromettere il sistema.
D: – Nel perimetro protetto qualcuno ha tentato di formare protocolli inaspettati per il server, per mandare il server in crash e, se tutto va bene, l'intero cluster... È possibile che sia così "positivo"?
MT: – Non ne ho mai sentito parlare. Non è un segreto che si possa mandare in tilt un server in questo modo. Mandare in tilt dall'interno, rimanendo autorizzato come utente che può scrivere qualcosa nel messaggio... In realtà non è possibile, perché verrà comunque verificato. È possibile disattivare questa autenticazione per gli utenti che non la desiderano – queste sono le loro problematiche; in parole povere, hanno abbattuto loro stessi le mura e si può infilare un elefante che calpesta... In generale, dai, puoi vestirti da tecnico, venire e tirarlo fuori!
D: – Grazie per la relazione. Sergey ("Yandex"). In "Mongo" c'è una costante che limita il numero dei membri votanti nel Replica Set, e questa costante è pari a 7 (sette). Perché è una costante? Perché non è un parametro qualsiasi?
MT: – Nel Replica Set abbiamo anche 40 nodi. Ci deve sempre essere una maggioranza. Non so quale sia la versione...
D: – Nel Replica Set è possibile avviare membri non votanti, ma i votanti sono al massimo 7. Come ci si comporta in caso di disattivazione, se il nostro Replica Set è distribuito su 3 data center? Un data center può spegnersi facilmente, e un'altra macchina può andare offline.
MT: – Questo è già leggermente al di fuori della relazione. È una domanda generale. Forse posso parlarne più tardi.


Un po' di pubblicità 🙂
Grazie per rimanere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci a qualcuno. , unica alternativa ai server entry-level, concepita da noi per te: (sono 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! Leggi di
Fonte: habr.com
