{"id":37956,"date":"2019-10-31T22:20:47","date_gmt":"2019-10-31T19:20:47","guid":{"rendered":"https:\/\/prohoster.info\/blog\/newsql-nosql-acid\/"},"modified":"2019-10-31T22:20:47","modified_gmt":"2019-10-31T19:20:47","slug":"newsql-nosql-acid","status":"publish","type":"post","link":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/newsql-nosql-acid","title":{"rendered":"NewSQL = NoSQL + ACID","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/973145efa566bca10ffe7ba95733a1d5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nFino a poco tempo fa, in Odnoklassniki, circa 50 TB di dati trattati in tempo reale erano memorizzati in SQL Server. Per un tale volume, garantire accesso veloce, affidabile e anche ridondante a un centro dati utilizzando un database SQL \u00e8 praticamente impossibile. Di solito, in questi casi, si utilizza uno dei sistemi di archiviazione NoSQL, ma non tutto pu\u00f2 essere trasferito in NoSQL: alcune entit\u00e0 richiedono garanzie di transazioni ACID. <\/p>\n<p>Questo ci ha portato all'uso di un sistema di archiviazione NewSQL, cio\u00e8 un database che offre tolleranza ai guasti, scalabilit\u00e0 e velocit\u00e0 dei sistemi NoSQL, ma mantenendo le garanzie ACID caratteristiche dei sistemi tradizionali. Ci sono poche soluzioni industriali funzionanti di questa nuova classe, quindi abbiamo realizzato un tale sistema da soli e lo abbiamo messo in funzione nel settore. <\/p>\n<p>Come funziona e cosa \u00e8 stato realizzato \u2014 leggi sotto.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nOggi l'audience mensile di \"Odnoklassniki\" supera i 70 milioni di visitatori unici. Noi <noindex><a rel=\"nofollow\" href=\"https:\/\/www.similarweb.com\/top-websites\/category\/internet-and-telecom\/social-network\">siamo tra le cinque<\/a><\/noindex> principali reti sociali del mondo, e tra i primi venti siti dove gli utenti trascorrono pi\u00f9 tempo. L'infrastruttura di \"OK\" gestisce carichi molto elevati: oltre un milione di richieste HTTP\/secondo sui front-end. Parti del parco server, che conta oltre 8000 unit\u00e0, si trovano vicine tra loro \u2014 in quattro data center a Mosca, il che consente di garantire una latenza di rete inferiore a 1 ms tra di essi.<\/p>\n<p>Utilizziamo Cassandra dal 2010, a partire dalla versione 0.6. Oggi sono in uso diverse dozzine di cluster. Il cluster pi\u00f9 veloce gestisce oltre 4 milioni di operazioni al secondo, mentre il pi\u00f9 grande memorizza 260 TB. <\/p>\n<p>Tuttavia, tutto ci\u00f2 sono normali cluster NoSQL utilizzati per la memorizzazione <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A1%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B2_%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D0%BE%D0%BC_%D1%81%D1%87%D1%91%D1%82%D0%B5\">di dati poco coerenti.<\/a><\/noindex> Volevamo invece sostituire il principale sistema di archiviazione coerente, Microsoft SQL Server, utilizzato fin dalla fondazione di \"Odnoklassniki\". Il sistema di archiviazione era composto da oltre 300 macchine SQL Server Standard Edition, su cui erano memorizzati 50 TB di dati \u2014 entit\u00e0 aziendali. Questi dati vengono modificati nell'ambito di transazioni ACID e richiedono <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">alta coerenza.<\/a><\/noindex>.<\/p>\n<p>Per distribuire i dati tra i nodi SQL Server abbiamo utilizzato sia il partizionamento verticale che quello orizzontale. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Partition_(database)\">partizionamento<\/a><\/noindex> (sharding). Storicamente abbiamo utilizzato uno schema semplice di sharding dei dati: a ciascuna entit\u00e0 veniva assegnato un token - una funzione dall'ID dell'entit\u00e0. Le entit\u00e0 con lo stesso token erano allocate su un unico server SQL. La relazione di tipo master-detail era realizzata in modo tale che i token del record principale e del record derivato coincidessero sempre e fossero nello stesso server. Nella rete sociale quasi tutti i record sono generati a nome dell'utente, il che significa che tutti i dati dell'utente all'interno di una singola sottosistema funzionale sono memorizzati su un unico server. Cio\u00e8, quasi sempre le transazioni commerciali coinvolgevano tabelle di un unico server SQL, il che consentiva di garantire la coerenza dei dati tramite transazioni ACID locali, senza la necessit\u00e0 di utilizzare <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">lente e inaffidabili<\/a><\/noindex> transazioni ACID distribuite.<\/p>\n<p>Grazie allo sharding e per accelerare il funzionamento di SQL:<\/p>\n<ul>\n<li>Non utilizziamo vincoli di chiave esterna, poich\u00e9 durante lo sharding l'ID dell'entit\u00e0 potrebbe trovarsi su un altro server.<\/li>\n<li>Non utilizziamo stored procedure e trigger a causa del carico aggiuntivo sulla CPU del DBMS.<\/li>\n<li>Non utilizziamo JOIN poich\u00e9 tutto quanto sopra e un gran numero di letture casuali dal disco.<\/li>\n<li>Fuori dalla transazione, per ridurre i deadlock utilizziamo il livello di isolamento Read Uncommitted.<\/li>\n<li>Eseguiamo solo transazioni brevi (in media pi\u00f9 brevi di 100 ms).<\/li>\n<li>Non utilizziamo UPDATE e DELETE multiriga a causa dell'elevato numero di deadlock - aggiorniamo solo un record alla volta.<\/li>\n<li>Le richieste vengono sempre eseguite solo sugli indici - una richiesta con un piano di scansione completa della tabella per noi significa sovraccarico del DB e il suo crash.<\/li>\n<\/ul>\n<p>\nQuesti passaggi hanno permesso di estrarre quasi il massimo delle prestazioni dai server SQL. Tuttavia, i problemi aumentavano sempre di pi\u00f9. Esaminiamoli.<\/p>\n<h2>Problemi con SQL<\/h2>\n<p><\/p>\n<ul>\n<li>Poich\u00e9 abbiamo utilizzato uno sharding personalizzato, l'aggiunta di nuovi shard veniva eseguita manualmente dagli amministratori. Per tutto questo tempo, le repliche scalabili dei dati non gestivano le richieste. <\/li>\n<li>Con l'aumento del numero di record nella tabella, la velocit\u00e0 di inserimento e modifica diminuisce; aggiungendo indici a una tabella esistente, la velocit\u00e0 diminuisce notevolmente, la creazione e la ricreazione degli indici avviene con downtime.<\/li>\n<li>La presenza in produzione di un numero ridotto di Windows per SQL Server complica la gestione dell'infrastruttura.<\/li>\n<\/ul>\n<p>\nMa il problema principale \u00e8 \u2014 <\/p>\n<h2>Resilienza<\/h2>\n<p>\nUn server SQL classico ha bassa capacit\u00e0 di tolleranza ai guasti. Supponiamo che tu abbia solo un server di database e che questo si guasti una volta ogni tre anni. Durante questo tempo, il sito non funziona per 20 minuti, il che \u00e8 accettabile. Se hai 64 server, il sito non funziona una volta ogni tre settimane. E se hai 200 server, il sito non funziona ogni settimana. Questo \u00e8 un problema. <\/p>\n<p>Cosa si pu\u00f2 fare per aumentare la tolleranza ai guasti di un server SQL? Wikipedia ci suggerisce di costruire <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/High-availability_cluster\">un cluster ad alta disponibilit\u00e0<\/a><\/noindex>: dove nel caso di guasto di uno qualsiasi dei componenti, c'\u00e8 una copia di riserva.<\/p>\n<p>Ci\u00f2 richiede un parco di attrezzature costose: duplicazioni multiple, fibra ottica, storage condiviso, e anche l'attivazione delle riserve funziona in modo inaffidabile: circa il 10% delle attivazioni si conclude con il fallimento della node di riserva in parallelo alla node principale. <\/p>\n<p>Ma il principale svantaggio di un cluster ad alta disponibilit\u00e0 \u00e8 l'assenza di disponibilit\u00e0 in caso di guasto del data center in cui si trova. 'Odnoklassniki' ha quattro data center, e dobbiamo garantire il funzionamento in caso di un guasto totale in uno di essi.<\/p>\n<p>Per questo si potrebbe applicare <noindex><a rel=\"nofollow\" href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms151196.aspx\">la replicazione Multi-Master<\/a><\/noindex> integrata in SQL Server. Questa soluzione \u00e8 molto pi\u00f9 costosa a causa del costo del software e soffre di problemi noti con la replicazione: ritardi imprevedibili nelle transazioni durante la replicazione sincrona e ritardi nell'applicazione delle repliche (e, di conseguenza, modifiche perse) in quella asincrona. Si presume che <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/replication\/transactional\/peer-to-peer-conflict-detection-in-peer-to-peer-replication\">la risoluzione manuale dei conflitti<\/a><\/noindex> renda questa opzione completamente inapplicabile per noi.<\/p>\n<p>Tutti questi problemi richiedevano una soluzione drastica e abbiamo iniziato ad analizzarli in dettaglio. Qui dobbiamo familiarizzare con ci\u00f2 che fa principalmente SQL Server: le transazioni.<\/p>\n<h2>Transazione semplice<\/h2>\n<p>\nConsideriamo la pi\u00f9 semplice, dal punto di vista di un programmatore SQL applicativo, transazione: aggiunta di una foto a un album. Gli album e le foto vengono memorizzati in tavole diverse. Un album ha un contatore di foto pubbliche. Pertanto, tale transazione si suddivide nei seguenti passaggi: <\/p>\n<ol>\n<li>Blocchiamo l'album per chiave.<\/li>\n<li>Creiamo un record nella tabella delle foto. <\/li>\n<li>Se la foto ha uno status pubblico, incrementiamo nel'album il contatore delle foto pubbliche, aggiorniamo il record e confermiamo la transazione.<\/li>\n<\/ol>\n<p>\nO in forma di pseudocodice:<\/p>\n<pre><code>TX.start(\"Albums\", id);\nAlbum album = albums.lock(id);\nPhoto photo = photos.create(\u2026);\n\nif (photo.status == PUBLIC ) {\n    album.incPublicPhotosCount();\n}\nalbum.update();\n\nTX.commit();<\/code><\/pre>\n<p>\nVediamo che lo scenario di transazione aziendale pi\u00f9 comune \u00e8 leggere i dati dal database nella memoria del server delle applicazioni, modificare qualcosa e salvare i nuovi valori nel database. Di solito, in una tale transazione aggiorniamo diverse entit\u00e0, diverse tabelle. <\/p>\n<p>Durante l'esecuzione di una transazione, pu\u00f2 verificarsi una modifica concorrente degli stessi dati da un altro sistema. Ad esempio, il sistema antispam potrebbe decidere che un utente \u00e8 sospetto e quindi tutte le foto dell'utente non devono pi\u00f9 essere pubbliche, devono essere inviate in moderazione, il che significa cambiare photo.status in un altro valore e aggiornare i contatori corrispondenti. \u00c8 ovvio che se questa operazione avviene senza garanzie di atomicit\u00e0 e isolamento delle modifiche concorrenti, come in <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">ACID<\/a><\/noindex>, il risultato non sar\u00e0 quello necessario: o il contatore delle foto mostrer\u00e0 un valore errato, o non tutte le foto verranno inviate in moderazione. <\/p>\n<p>Codici simili, che manipolano varie entit\u00e0 aziendali all'interno di una singola transazione, sono stati scritti per tutto il tempo di esistenza di Odnoklassniki. Dall'esperienza di migrazioni verso NoSQL con <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Eventual_consistency\">Coerenza Eventuale<\/a><\/noindex> , sappiamo che le maggiori complessit\u00e0 (e costi in termini di tempo) derivano dalla necessit\u00e0 di sviluppare codice volto a mantenere la coerenza dei dati. Pertanto, il principale requisito per il nuovo archivio era garantire la logica applicativa di vere transazioni ACID. <\/p>\n<p>Altrettanto importanti erano i seguenti requisiti:<\/p>\n<ul>\n<li>In caso di guasto del data center, devono essere accessibili sia la lettura che la scrittura nel nuovo archivio.<\/li>\n<li>Mantenere l'attuale velocit\u00e0 di sviluppo. Cio\u00e8, quando si lavora con il nuovo archivio, la quantit\u00e0 di codice deve essere approssimativamente la stessa, non deve esserci bisogno di aggiungere qualcosa all'archivio, sviluppare algoritmi di risoluzione dei conflitti, mantenere indici secondari, ecc. <\/li>\n<li>La velocit\u00e0 di funzionamento del nuovo archivio deve essere sufficientemente alta sia per la lettura dei dati che per l'elaborazione delle transazioni, il che significa che soluzioni accademiche rigorose, universali, ma lente, come ad esempio <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">i commit a due fasi<\/a><\/noindex>.<\/li>\n<li>Ridimensionamento automatico in tempo reale. <\/li>\n<li>Utilizzo di server comuni e a basso costo, senza necessit\u00e0 di acquistare hardware esotico. <\/li>\n<li>Possibilit\u00e0 di sviluppare lo storage con gli sviluppatori dell'azienda. In altre parole, si privilegiavano soluzioni interne o basate su codice aperto, preferibilmente in Java.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Soluzioni, soluzioni<\/h2>\n<p>\nAnalizzando le possibili soluzioni, siamo giunti a due scelte architettoniche possibili:<\/p>\n<p>La prima \u00e8 prendere un qualsiasi server SQL e implementare la resilienza desiderata, il meccanismo di scalabilit\u00e0, un cluster tollerante ai guasti, la risoluzione dei conflitti e transazioni ACID distribuite, affidabili e veloci. Abbiamo valutato questa opzione come piuttosto non banale e laboriosa.<\/p>\n<p>La seconda opzione \u00e8 prendere uno storage NoSQL gi\u00e0 pronto con scalabilit\u00e0 implementata, un cluster tollerante ai guasti, la risoluzione dei conflitti e implementare noi stessi le transazioni e SQL. A prima vista, anche solo l'implementazione di SQL, per non parlare delle transazioni ACID, sembra un compito che richiede anni. Ma poi abbiamo capito che il set di funzionalit\u00e0 SQL che utilizziamo nella pratica \u00e8 lontano dall'ANSI SQL tanto quanto <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Apache_Cassandra#Cassandra_Query_Language\">Cassandra CQL<\/a><\/noindex> \u00e8 lontano dall'ANSI SQL. Osservando pi\u00f9 attentamente il CQL, abbiamo capito che \u00e8 abbastanza vicino a quello di cui abbiamo bisogno.<\/p>\n<h2>Cassandra e CQL<\/h2>\n<p>\nQuindi, cosa rende interessante Cassandra e quali funzionalit\u00e0 offre?<\/p>\n<p>In primo luogo, \u00e8 possibile creare tabelle con supporto per diversi tipi di dati, \u00e8 possibile effettuare SELECT o UPDATE sulla chiave primaria.<\/p>\n<pre><code>CREATE TABLE photos (id bigint KEY, owner bigint,\u2026);\nSELECT * FROM photos WHERE id=?;\nUPDATE photos SET \u2026 WHERE id=?;<\/code><\/pre>\n<p>\nPer garantire la coerenza dei dati delle repliche, Cassandra utilizza <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Quorum_(distributed_computing)\">un approccio di quorum.<\/a><\/noindex>. Nel caso pi\u00f9 semplice, ci\u00f2 significa che quando si posizionano tre repliche dello stesso dato su nodi diversi del cluster, la scrittura \u00e8 considerata riuscita se la maggior parte dei nodi (ossia due su tre) ha confermato il successo di questa operazione di scrittura. I dati della riga sono considerati coerenti se, durante la lettura, la maggior parte dei nodi sono stati interrogati e hanno confermato i dati. Pertanto, con tre repliche, viene garantita la piena e immediata coerenza dei dati in caso di guasto di un nodo. Questo approccio ci ha permesso di implementare uno schema ancora pi\u00f9 affidabile: inviare sempre richieste a tutte e tre le repliche, attendendo la risposta dalle due pi\u00f9 veloci. La risposta tardiva della terza replica viene quindi scartata. Il nodo che ha tardato a rispondere pu\u00f2 avere gravi problemi: rallentamenti, garbage collection in JVM, direct memory reclaim nel kernel linux, guasti hardware, disconnessione dalla rete. Tuttavia, ci\u00f2 non influisce sulle operazioni del cliente e sui dati.<\/p>\n<p>L'approccio in cui ci rivolgiamo a tre nodi, ma riceviamo risposta da due, \u00e8 chiamato <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Speculative_execution\">speculazione<\/a><\/noindex>: la richiesta per repliche in pi\u00f9 viene inviata prima che avvenga il \"fallimento\". <\/p>\n<p>Un altro vantaggio di Cassandra \u00e8 il Batchlog: un meccanismo che garantisce l'applicazione totale o l'assenza totale di un pacchetto di modifiche che apporti. Questo ci consente di realizzare la A in ACID \u2014 atomicit\u00e0 out of the box.<\/p>\n<p>La cosa pi\u00f9 vicina alle transazioni in Cassandra \u00e8 quella che viene chiamata &#171;<noindex><a rel=\"nofollow\" href=\"https:\/\/docs.datastax.com\/en\/cql\/3.3\/cql\/cql_using\/useInsertLWT.html\">transazioni leggere<\/a><\/noindex>&#171;. Ma sono lontane dalle vere transazioni ACID: in realt\u00e0, questa \u00e8 la possibilit\u00e0 di fare <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Compare-and-swap\">CAS<\/a><\/noindex> su dati di una sola riga, utilizzando il consenso nel pesante protocollo Paxos. Pertanto, la velocit\u00e0 di tali transazioni non \u00e8 elevata. <\/p>\n<h2>Cosa ci mancava in Cassandra<\/h2>\n<p>\nQuindi dovevamo implementare in Cassandra vere transazioni ACID. Con cui potremmo facilmente realizzare altre due comode funzionalit\u00e0 delle DBMS classiche: indici rapidi e coerenti, il che ci consentirebbe di eseguire selezioni di dati non solo in base alla chiave primaria e un generatore normale di ID monotoni autoincrementabili.<\/p>\n<h4>C*One<\/h4>\n<p>\nCos\u00ec nacque un nuovo DBMS <b>C*One<\/b>, composto da tre tipi di nodi server:<\/p>\n<ul>\n<li>Storage \u2014 server Cassandra (quasi) standard, responsabili della memorizzazione dei dati sui dischi locali. Man mano che cresce il carico e il volume dei dati, il loro numero pu\u00f2 essere facilmente scalato fino a decine e centinaia.<\/li>\n<li>I coordinatori delle transazioni garantiscono l'esecuzione delle transazioni. <\/li>\n<li>I client sono i server delle applicazioni che implementano operazioni aziendali e avviano transazioni. Tali client possono essere migliaia.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/f3809d89404ae596c62b642dbdb9e431.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI server di tutti i tipi fanno parte di un cluster comune, utilizzano il protocollo di messaggistica interno di Cassandra per comunicare tra loro e <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Gossip_protocol\">gossip<\/a><\/noindex> per scambiare informazioni di cluster. Grazie a Heartbeat, i server possono rilevare i guasti reciproci, mantenere un'unica schema dei dati - tabelle, la loro struttura e replica; schema di partizionamento, topologia del cluster, e cos\u00ec via.<\/p>\n<h4>Clienti<\/h4>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/80f83358f215cf59db3b657ba1083420.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nInvece dei driver standard viene utilizzata la modalit\u00e0 Fat Client. Un nodo di questo tipo non memorizza dati, ma pu\u00f2 fungere da coordinatore dell'esecuzione delle richieste, ovvero il Client stesso esegue la funzione di coordinatore delle sue richieste: interroga le repliche del repository e risolve i conflitti. Questo \u00e8 non solo pi\u00f9 affidabile e veloce rispetto al driver standard, che richiede comunicazioni con un coordinatore remoto, ma consente anche di gestire l'invio delle richieste. Al di fuori di una transazione aperta sul client, le richieste vengono dirette ai repository. Se il client ha aperto una transazione, tutte le richieste nell'ambito della transazione vengono inviate al coordinatore delle transazioni.<br \/>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/709bf227eeb8f71abeff703a72780efd.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h2>Coordinatore delle transazioni C*One<\/h2>\n<p>\nIl coordinatore \u00e8 ci\u00f2 che abbiamo realizzato per C*One da zero. \u00c8 responsabile della gestione delle transazioni, dei blocchi e dell'ordine di applicazione delle transazioni.<\/p>\n<p>Per ogni transazione gestita, il coordinatore genera un timestamp: ogni successivo \u00e8 maggiore di quello della transazione precedente. Poich\u00e9 in Cassandra il sistema di risoluzione dei conflitti si basa sui timestamp (tra due record in conflitto, quello pi\u00f9 recente \u00e8 considerato valido), il conflitto sar\u00e0 sempre risolto a favore della transazione successiva. In questo modo abbiamo implementato <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A7%D0%B0%D1%81%D1%8B_%D0%9B%D1%8D%D0%BC%D0%BF%D0%BE%D1%80%D1%82%D0%B0\">orologi di Lamport<\/a><\/noindex> \u2014 un modo economico per risolvere i conflitti in un sistema distribuito.<\/p>\n<h2>Blocchi<\/h2>\n<p>\nPer garantire l'isolamento, abbiamo deciso di utilizzare il modo pi\u00f9 semplice: blocchi pessimisti basati sulla chiave primaria del record. In altre parole, nella transazione, il record deve prima essere bloccato, quindi letto, modificato e salvato. Solo dopo un commit riuscito il record pu\u00f2 essere sbloccato affinch\u00e9 le transazioni concorrenti possano utilizzarlo.<\/p>\n<p>L'implementazione di tale blocco \u00e8 semplice in un ambiente non distribuito. In un sistema distribuito ci sono due percorsi principali: o implementare un blocco distribuito nel cluster, oppure distribuire le transazioni in modo che le transazioni che coinvolgono una registrazione siano sempre gestite dallo stesso coordinatore.<\/p>\n<p>Poich\u00e9 nel nostro caso i dati sono gi\u00e0 distribuiti in gruppi di transazioni locali in SQL, \u00e8 stato deciso di assegnare ai coordinatori gruppi di transazioni locali: un coordinatore esegue tutte le transazioni con un token da 0 a 9, il secondo con un token da 10 a 19, e cos\u00ec via. Di conseguenza, ciascuno degli esemplari del coordinatore diventa il master del gruppo di transazioni. <\/p>\n<p>Allora i blocchi possono essere implementati sotto forma di una semplice HashMap in memoria del coordinatore.<\/p>\n<h2>Guasti dei coordinatori<\/h2>\n<p>\nPoich\u00e9 un coordinatore gestisce esclusivamente un gruppo di transazioni, \u00e8 molto importante determinare rapidamente il fatto della sua indisponibilit\u00e0, affinch\u00e9 il tentativo di riesecuzione della transazione rientri nel timeout. Per garantire che ci\u00f2 avvenga in modo rapido e affidabile, abbiamo applicato un protocollo di heartbeat a quorum completo:<\/p>\n<p>In ogni data center sono presenti almeno due nodi coordinatori. Periodicamente, ciascun coordinatore invia un messaggio di heartbeat agli altri coordinatori, informandoli sul proprio stato operativo, nonch\u00e9 sui messaggi di heartbeat ricevuti dagli altri coordinatori nel cluster. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5c337e53815cad0a2071b160d98d42fa.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nRicevendo informazioni simili dagli altri nei loro messaggi di heartbeat, ciascun coordinatore decide quali nodi del cluster stanno funzionando e quali no, seguendo il principio del quorum: se il nodo X riceve dalla maggior parte dei nodi del cluster informazioni sulla ricezione normale dei messaggi dal nodo Y, significa che Y \u00e8 attivo. E viceversa, non appena la maggioranza segnala la mancanza di messaggi dal nodo Y, significa che Y ha fallito. \u00c8 curioso notare che se il quorum informa il nodo X di non ricevere pi\u00f9 messaggi da esso, il nodo stesso X sar\u00e0 considerato fuori servizio.<\/p>\n<p>I messaggi Heartbeat vengono inviati con grande frequenza, circa 20 volte al secondo, con un intervallo di 50 ms. In Java \u00e8 difficile garantire una risposta dell'applicazione entro 50 ms a causa della durata comparabile delle pause, causate dal garbage collector. Siamo riusciti a ottenere un tale tempo di risposta utilizzando il garbage collector G1, che consente di specificare un obiettivo per la durata delle pause del GC. Tuttavia, a volte, abbastanza raramente, le pause del garbage collector superano i 50 ms, il che pu\u00f2 portare a falsi rilevamenti di guasti. Per evitare ci\u00f2, il coordinatore non segnala il guasto di un nodo remoto alla perdita del primo messaggio heartbeat da esso, ma solo se ne mancano pi\u00f9 di uno consecutivo. In questo modo siamo riusciti a ottenere il rilevamento del guasto del nodo coordinatore entro 200 ms. <\/p>\n<p>Ma \u00e8 poco per capire rapidamente quale nodo ha smesso di funzionare. Bisogna fare qualcosa in proposito. <\/p>\n<h2>Ridondanza<\/h2>\n<p>\nLo schema classico prevede, in caso di guasto del master, di avviare le elezioni di un nuovo master mediante uno dei<noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_Raft\"> popolari<\/a><\/noindex> <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D0%B0%D0%BA%D1%81%D0%BE%D1%81\">universali<\/a><\/noindex> algoritmi. Tuttavia, tali algoritmi hanno ben note problematiche di convergenza temporale e durata del processo elettorale stesso. Siamo riusciti a evitare tali ritardi aggiuntivi mediante uno schema di sostituzione dei coordinatori in una rete completamente connessa:<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5e2db258fa2200026390a73a414dfe81.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSupponiamo di voler eseguire una transazione nel gruppo 50. Definiamo in anticipo lo schema di sostituzione, cio\u00e8 quali nodi eseguiranno le transazioni del gruppo 50 in caso di guasto del coordinatore principale. Il nostro obiettivo \u00e8 mantenere il funzionamento del sistema in caso di guasto del data center. Definiamo che il primo riserva sar\u00e0 un nodo di un altro data center e la seconda riserva sar\u00e0 un nodo del terzo. Questo schema viene scelto una volta e non cambia fino a quando non cambia la topologia del cluster, ovvero fino a quando non entrano nuovi nodi (cosa che accade molto raramente). L'ordine di scelta di un nuovo master attivo in caso di guasto del vecchio sar\u00e0 sempre questo: il primo riserva diventer\u00e0 il master attivo e, se anche lui smette di funzionare, il secondo riserva. <\/p>\n<p>Questo schema \u00e8 pi\u00f9 affidabile di un algoritmo universale, poich\u00e9 per attivare un nuovo master \u00e8 sufficiente determinare il fatto del guasto del vecchio.<\/p>\n<p>Ma come faranno i clienti a capire quale dei maestri sta lavorando in questo momento? In 50 ms \u00e8 impossibile inviare informazioni a migliaia di clienti. Pu\u00f2 succedere che un cliente invii una richiesta per aprire una transazione senza sapere che quel maestro non \u00e8 pi\u00f9 attivo, e la richiesta rimarr\u00e0 in attesa. Per evitare ci\u00f2, i clienti inviano speculativamente la richiesta di apertura della transazione al maestro del gruppo e ai suoi due riservi, ma risponder\u00e0 a questa richiesta solo colui che \u00e8 il maestro attivo in quel momento. Tutta la comunicazione successiva nell'ambito della transazione sar\u00e0 effettuata solo con il maestro attivo.<\/p>\n<p>I maestri riservisti collocano le richieste ricevute per transazioni non proprie in una coda di transazioni non nate, dove rimangono per un certo periodo. Se il maestro attivo termina, un nuovo maestro gestisce le richieste di apertura delle transazioni dalla sua coda e risponde al cliente. Se il cliente ha gi\u00e0 aperto una transazione con il vecchio maestro, la seconda risposta viene ignorata (e, ovviamente, tale transazione non si concluder\u00e0 e verr\u00e0 ripetuta dal cliente).<\/p>\n<h2>Come funziona una transazione<\/h2>\n<p>\nSupponiamo che il cliente abbia inviato al coordinatore una richiesta di apertura di una transazione per una certa entit\u00e0 con una certa chiave primaria. Il coordinatore blocca questa entit\u00e0 e la colloca nella tabella di blocco in memoria. Se necessario, il coordinatore legge quest'entit\u00e0 dal repository e salva i dati ottenuti nello stato della transazione in memoria.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/246b4a5c68a4cc886b87af784c16b9dc.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando un cliente vuole modificare i dati nella transazione, invia al coordinatore una richiesta di modifica dell'entit\u00e0, e questi colloca i nuovi dati nella tabella di stato delle transazioni in memoria. A questo punto, la registrazione \u00e8 terminata: non viene eseguita alcuna registrazione nel repository.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/4400a91af30eb71134978ff27474f745.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando un cliente richiede i propri dati modificati all'interno di una transazione attiva, il coordinatore agisce in questo modo: <\/p>\n<ul>\n<li>se l'ID \u00e8 gi\u00e0 presente nella transazione, i dati vengono presi dalla memoria; <\/li>\n<li>se l'ID non \u00e8 presente in memoria, i dati mancanti vengono letti dai nodi di archiviazione, uniti con quelli gi\u00e0 presenti in memoria, e il risultato viene fornito al cliente. <\/li>\n<\/ul>\n<p>\nIn questo modo, il cliente pu\u00f2 leggere le proprie modifiche, mentre gli altri clienti non vedono tali modifiche, poich\u00e9 sono memorizzate solo nella memoria del coordinatore e non sono ancora disponibili nei nodi di Cassandra.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/c93fd7e8393f431a976f1f56b95e227c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nQuando il cliente invia un commit, lo stato presente in memoria del servizio viene salvato dal coordinatore in un batch registrato, e quindi inviato ai repository Cassandra sotto forma di batch registrato. I repository compiono tutto il necessario affinch\u00e9 questo pacchetto venga applicato in modo atomico (completamente), e restituiscono una risposta al coordinatore, che a sua volta libera i blocchi e conferma il successo della transazione al cliente.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ab20487ba53ed88e2f560a2249b5d6f9.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nE per annullare, al coordinatore basta liberare la memoria occupata dallo stato della transazione.<\/p>\n<p>A seguito delle modifiche descritte sopra, abbiamo implementato i principi ACID:<\/p>\n<ul>\n<li><b>Atomicit\u00e0<\/b>. Questa \u00e8 la garanzia che nessuna transazione sar\u00e0 registrata nel sistema in modo parziale; tutte le sue sotto-operazioni saranno eseguite oppure nessuna di esse verr\u00e0 eseguita. Questo principio \u00e8 rispettato da noi attraverso il batch registrato in Cassandra.<\/li>\n<li><b>Coerenza<\/b>. Ogni transazione andata a buon fine definisce solo i risultati ammissibili. Se dopo aver aperto una transazione e aver eseguito parte delle operazioni si scopre che il risultato non \u00e8 ammissibile, avviene un rollback.<\/li>\n<li><b>Isolamento<\/b>. Durante l'esecuzione della transazione, le transazioni parallele non devono influenzarne il risultato. Le transazioni concorrenti sono isolate mediante blocchi pessimisti sul coordinatore. Per le letture al di fuori della transazione viene rispettato il principio di isolamento a livello di Read Committed.<\/li>\n<li><b>Resilienza<\/b>. Indipendentemente dai problemi ai livelli inferiori \u2014 come un'interruzione di corrente, guasti hardware \u2014 le modifiche apportate da una transazione completata con successo devono rimanere salvate dopo il ripristino del funzionamento. <\/li>\n<\/ul>\n<p><\/p>\n<h2>Lettura per indici<\/h2>\n<p>\nPrendiamo una semplice tabella: <\/p>\n<pre><code>CREATE TABLE photos (\nid bigint primary key,\nowner bigint,\nmodified timestamp,\n\u2026)<\/code><\/pre>\n<p>\nQuesta tabella ha un ID (chiave primaria), un proprietario e una data di modifica. Dobbiamo effettuare una query molto semplice \u2014 selezionare i dati in base all'owner con la data di modifica \"nell'ultimo giorno\". <\/p>\n<pre><code>SELECT *\nWHERE owner=?\nAND modified&gt;?<\/code><\/pre>\n<p>\nAffinch\u00e9 una simile query venga eseguita rapidamente, in un classico DBMS SQL \u00e8 necessario costruire un indice sulle colonne (owner, modified). Possiamo fare ci\u00f2 molto facilmente, poich\u00e9 ora abbiamo garanzie ACID!<\/p>\n<h2>Indici in C*One<\/h2>\n<p>\nEsiste una tabella di base con foto, in cui l'ID della registrazione \u00e8 la chiave primaria. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/9f5d7c5f66882666a7ae8219f710beeb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPer l'indice C*One crea una nuova tabella, che \u00e8 una copia della tabella originale. La chiave coincide con l'espressione indice, mentre include anche la chiave primaria della registrazione dalla tabella originale:<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL + ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ce6df1c8407bca7ddc61fd88141455e6.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOra la richiesta per \u00abil proprietario nelle ultime 24 ore\u00bb pu\u00f2 essere riscritta come select da un'altra tabella:<\/p>\n<pre><code>SELECT * FROM i1_test\nWHERE owner=?\nAND modified&gt;?<\/code><\/pre>\n<p>\nLa coerenza dei dati della tabella originale photos e dell'indice i1 \u00e8 mantenuta automaticamente dal coordinatore. Basandosi solo sullo schema dei dati, al verificarsi di una modifica, il coordinatore genera e memorizza il cambiamento non solo della tabella principale, ma anche delle sue copie. Non vengono eseguite azioni aggiuntive sulla tabella dell'indice, i log non vengono letti, e non vengono utilizzati blocchi. Questo significa che l'aggiunta di indici consuma quasi nessuna risorsa e non influisce praticamente sulla velocit\u00e0 di applicazione delle modifiche.<\/p>\n<p>Con ACID siamo riusciti a implementare indici \u00abcome in SQL\u00bb. Essi possiedono coerenza, possono scalare, funzionano rapidamente, possono essere composti e integrati nel linguaggio di query CQL. Per supportare gli indici non \u00e8 necessario apportare modifiche al codice applicativo. \u00c8 tutto semplice, come in SQL. E cosa pi\u00f9 importante, gli indici non influenzano la velocit\u00e0 di esecuzione delle modifiche nella tabella originale delle transazioni.<\/p>\n<h2>Risultato ottenuto<\/h2>\n<p>\nAbbiamo sviluppato C*One tre anni fa e l'abbiamo messo in produzione. <\/p>\n<p>Cosa abbiamo ottenuto quindi? Valutiamolo prendendo ad esempio il sottosistema di elaborazione e archiviazione delle fotografie, uno dei tipi di dati pi\u00f9 importanti in una rete sociale. Non si tratta dei corpi delle fotografie, ma di tutte le varie meta-informazioni. Oggi, in \u00abOdnoklassniki\u00bb, ci sono circa 20 miliardi di tali registrazioni, il sistema gestisce 80.000 richieste di lettura al secondo, fino a 8.000 transazioni ACID al secondo relative alla modifica dei dati. <\/p>\n<p>Quando utilizzavamo SQL con replication factor = 1 (ma in RAID 10), la meta-informazione delle fotografie era memorizzata su un cluster ad alta disponibilit\u00e0 di 32 macchine con Microsoft SQL Server (pi\u00f9 11 di riserva). Sono stati riservati anche 10 server per archiviare i backup. In totale 50 macchine costose. Nel frattempo, il sistema operava a carico nominale, senza margine.<\/p>\n<p>Dopo la migrazione al nuovo sistema abbiamo ottenuto replication factor = 3 \u2014 una copia in ogni data center. Il sistema \u00e8 composto da 63 nodi di archiviazione Cassandra e 6 macchine coordinatrici, per un totale di 69 server. Ma queste macchine sono notevolmente pi\u00f9 economiche, il loro costo totale \u00e8 circa il 30% del costo del sistema su SQL. Nel frattempo, il carico si mantiene attorno al 30%.<\/p>\n<p>Con l'implementazione di C*One sono diminuite anche le latenza: in SQL l'operazione di scrittura richiedeva circa 4,5 ms. In C*One \u2014 circa 1,6 ms. La durata della transazione \u00e8 mediamente inferiore a 40 ms, il commit avviene in 2 ms, la durata di lettura e scrittura \u2014 in media 2 ms. Il 99\u00b0 percentile \u2014 solo 3-3,1 ms, il numero di timeout \u00e8 diminuito di 100 volte \u2014 tutto grazie all'ampio utilizzo delle speculazioni. <\/p>\n<p>Al momento, la maggior parte dei nodi SQL Server \u00e8 stata dismessa, i nuovi prodotti vengono sviluppati esclusivamente utilizzando C*One. Abbiamo adattato C*One per funzionare nel nostro cloud <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/company\/odnoklassniki\/blog\/346868\/\">one-cloud<\/a><\/noindex>, il che ha permesso di accelerare il dispiegamento di nuovi cluster, semplificare la configurazione e automatizzare l'operativit\u00e0. Senza il codice sorgente sarebbe stato molto pi\u00f9 complicato e <\/p>\n<p>Attualmente stiamo lavorando alla migrazione di altri nostri archivi nel cloud \u2014 ma questa \u00e8 un'altra storia.<br \/>\n<br \/>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/odnoklassniki\/blog\/417593\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0445\u0440\u0430\u043d\u0438\u043b\u043e\u0441\u044c \u0432 SQL Server. \u0414\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043c\u0430 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u0438 \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439, \u0434\u0430 \u0435\u0449\u0435 \u0438 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a \u043e\u0442\u043a\u0430\u0437\u0443 \u0426\u041e\u0414 \u0434\u043e\u0441\u0442\u0443\u043f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f SQL \u0421\u0423\u0411\u0414, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e. \u041e\u0431\u044b\u0447\u043d\u043e \u0432 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043e\u0434\u043d\u043e \u0438\u0437 NoSQL-\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449, \u043d\u043e \u043d\u0435 \u0432\u0441\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432 NoSQL: \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":28484,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-37956","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/newsql-nosql-acid\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"it_IT\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/newsql-nosql-acid\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:20:47+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:20:47+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster","description":"Fino a poco tempo fa in Odnoklassniki gestivamo circa 50 TB di dati.","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/newsql-nosql-acid","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster","og:description":"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.","og:url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/newsql-nosql-acid","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:20:47+00:00","article:modified_time":"2019-10-31T19:20:47+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"37956","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-23 19:55:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:17:23","updated":"2026-01-23 19:55:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/37956","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=37956"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/37956\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/28484"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=37956"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=37956"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=37956"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}