Cassandra. Come non morire se conosci solo Oracle

Ciao, Habr.

Mi chiamo Misha Butrimov e vorrei raccontarvi un po 'di Cassandra. La mia storia sarà utile a chi non ha mai avuto esperienza con database NoSQL, poiché presenta molte peculiarità di implementazione e insidie di cui è importante essere a conoscenza. E se oltre a Oracle o a qualsiasi altro database relazionale non avete mai visto altro, queste informazioni vi salveranno la vita.

Cosa rende Cassandra così valida? È un database NoSQL progettato senza un singolo punto di guasto, che scala bene. Se hai bisogno di aggiungere qualche terabyte a un database, basta aggiungere nodi nel ring. Vuoi espanderlo in un altro data center? Aggiungi nodi al cluster. Vuoi aumentare i RPS elaborati? Aggiungi nodi al cluster. Funziona anche al contrario.

Cassandra. Come non morire se conosci solo Oracle

In cosa è ancora efficace? Nel gestire molte richieste. Ma quanto è molto? 10.000, 20.000, 30.000, 40.000 richieste al secondo è un numero relativamente basso. 100.000 richieste al secondo in scrittura è anche poco. Ci sono aziende che sostengono di gestire 2 milioni di richieste al secondo. A loro, probabilmente, bisognerà credere.

In linea di massima, Cassandra ha una grande differenza rispetto ai dati relazionali: non li assomiglia affatto. È molto importante tenerlo a mente.

Non tutto ciò che appare uguale funziona allo stesso modo.

Un giorno un collega è venuto da me e ha chiesto: «Ecco il linguaggio di query SQL di Cassandra, e c'è la select statement, c'è il where e c'è l'and. Scrivo le lettere e non funziona. Perché?». Se si considera Cassandra come un database relazionale, è il modo ideale per concludere in un autolesionismo violento. E non sto promuovendo, è vietato in Russia. Stai solo progettando qualcosa di sbagliato.

Ad esempio, un cliente viene da noi e dice: «Costruiamo un database per serie TV, oppure un database per un ricettario. Avremo piatti con ingredienti o un elenco di serie TV e attori». Noi rispondiamo con entusiasmo: «Facciamo così!». È solo questione di inviare un paio di byte, alcune tabelle e tutto è pronto, funzionerà molto velocemente e in modo affidabile. E tutto va bene finché i clienti non arrivano e dicono che le casalinghe risolvono anche il problema inverso: hanno un elenco di ingredienti e vogliono sapere quale piatto vogliono preparare. Sei finito.

Tutto perché Cassandra è un database ibrido: è sia chiave-valore che memorizza dati in colonne larghe. Parlando il linguaggio Java o Kotlin, si potrebbe descrivere così:

Map<RowKey, SortedMap>

Cioè una mappa, che contiene anche una mappa ordinata. La prima chiave di questa mappa è la Row key o Partition key — la chiave di partizionamento. La seconda chiave, che è la chiave per la mappa già ordinata, è la Clustering key.

Per illustrare la distribuzione del database, disegniamo tre nodi. Ora dobbiamo capire come distribuire i dati tra i nodi. Perché se mettiamo tutto in uno (ce ne possono essere mille, duemila, cinque — quanti ne vogliamo), questo non è proprio distribuzione. Pertanto, abbiamo bisogno di una funzione matematica che restituisca un numero. Semplicemente un numero, un int lungo, che rientri in un certo intervallo. E un nodo risponderà a un determinato intervallo, il secondo a un altro, e così via.

Cassandra. Come non morire se conosci solo Oracle

Questo numero viene ottenuto tramite una funzione hash, che viene applicata esattamente a ciò che chiamiamo la chiave di partizione. Questo è il campo specificato nella direttiva della chiave primaria ed è il campo che sarà la prima e principale chiave nella mappa. Definisce su quale nodo i dati andranno. La tabella viene creata in Cassandra con una sintassi quasi identica a quella di SQL:

CREATE TABLE users (
	user_id uu id,
	name text,
	year int,
	salary float,
	PRIMARY KEY(user_id)

)

La chiave primaria in questo caso consiste in una sola colonna, che è anche la chiave di partizione.

Come verranno distribuiti gli utenti? Parte andrà su un nodo, parte su un altro e parte su un terzo. Si ottiene una normale tabella hash, che è anche una mappa, che in Python è un dizionario, ed è una semplice struttura chiave-valore da cui possiamo leggere tutti i valori, leggere e scrivere per chiave.

Cassandra. Come non morire se conosci solo Oracle

Select: quando allow filtering si trasforma in full scan, o come non bisogna fare

Scriviamo qualche selezione: select * from users where userid = . Sembra proprio come in Oracle: scriviamo select, indichiamo le condizioni e tutto funziona, gli utenti vengono estratti. Ma se scegliamo, ad esempio, un utente con un anno di nascita specifico, Cassandra si lamenta di non poter eseguire la query. Perché non sa assolutamente nulla su come sono distribuiti i dati relativi all'anno di nascita — ha solo una colonna specificata come chiave. A questo punto dice: «Va bene, posso comunque eseguire questa query. Aggiungi allow filtering». Aggiungiamo la direttiva e tutto funziona. E in quel momento succede l'inevitabile.

Quando eseguiamo test con dati di esempio, tutto va bene. Ma quando si esegue una query in produzione, dove abbiamo, ad esempio, 4 milioni di registrazioni, la situazione non è così bella. Perché allow filtering è una direttiva che consente a Cassandra di raccogliere tutti i dati da questa tabella da tutti i nodi, di data center (se ce ne sono molti in questo cluster), e solo dopo filtrarli. È analogo a un Full Scan, e difficilmente qualcuno ne sarà entusiasta.

Se avessimo bisogno di utenti solo per identificatori, ci andrebbe bene. Ma a volte dobbiamo scrivere altre query e applicare altre restrizioni sul campione. Pertanto, ricordiamo: questa è tutta una mappa, che ha una chiave di partizionamento, ma al suo interno c'è una mappa ordinata.

E anche essa ha una chiave, che chiamiamo Clustering Key. Questa chiave, a sua volta, è composta da colonne che sceglieremo e tramite la quale Cassandra comprende come i dati siano fisicamente ordinati e resi disponibili su ogni nodo. Quindi, per un certo Partition key, il Clustering key dirà come inserire i dati in questo albero e quale posto occuperanno.

È davvero un albero, viene semplicemente chiamato un comparatore a cui passiamo un certo insieme di colonne come oggetto, e viene definito anche questo come un insieme di colonne.

CREATE TABLE users_by_year_salary_id (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY((year), salary, user_id)

Si prega di notare la direttiva Primary key, il cui primo argomento (nel nostro caso l'anno) è sempre la Partition key. Può consistere in una o più colonne, non importa. Se ci sono più colonne, deve essere messo di nuovo tra parentesi affinché il preprocessore del linguaggio capisca che si tratta di una Primary key, seguita da tutte le altre colonne — Clustering key. Queste ultime verranno quindi passate nel comparatore nell'ordine in cui compaiono. Cioè, la prima colonna è più significativa, la seconda è meno significativa e così via. Come scriviamo per le classi di dati, ad esempio, campi equals: elenchiamo i campi e specifichiamo quali sono più importanti e quali meno. In Cassandra, sono, per così dire, i campi della data class, a cui si applicherà l'equals scritto per essa.

Imponiamo l'ordinamento, imponiamo restrizioni

È importante ricordare che l'ordine di ordinamento (decrescente, crescente, non importa) è definito nel momento stesso in cui viene creato la chiave, e non potrà essere modificato successivamente. Essa determina fisicamente come saranno ordinati i dati e come si disporranno. Se si rende necessario modificare la Clustering key o l'ordine di ordinamento, sarà necessario creare una nuova tabella e trasferirvi i dati. Non sarà possibile farlo con una tabella esistente.

Cassandra. Come non morire se conosci solo Oracle

Abbiamo popolato la nostra tabella con gli utenti e abbiamo notato che sono stati disposti in anello prima per anno di nascita, e poi all'interno di ogni nodo per stipendio e per ID utente. Ora possiamo selezionare, imponendo delle restrizioni.

Riparte nuovamente il nostro funzionante where, and, e gli utenti ci vengono forniti, e tutto va di nuovo bene. Ma se proviamo a utilizzare solo una parte della Clustering key, e per di più quella meno significativa, Cassandra subito si lamenterà, dicendo che non può trovare nella nostra mappa un posto dove questo oggetto, il cui campo per il comparatore è null, si trova, e dove è collocato quello che abbiamo appena definito. Dovrò sollevare nuovamente tutti i dati da questo nodo e filtrare. E questo è analogo a una Full Scan all'interno del nodo, ed è un problema.

In qualsiasi situazione incerta, crea una nuova tabella.

Se vogliamo essere in grado di recuperare gli utenti per ID, età o stipendio, cosa fare? Niente. Basta utilizzare due tabelle. Se sarà necessario recuperare gli utenti in tre modi diversi, le tabelle saranno tre. Sono finiti i tempi in cui risparmiavamo spazio su disco. Questo è il più economico delle risorse. Costa molto meno del tempo di risposta, che può essere letale per l'utente. È molto più piacevole per l'utente ricevere qualcosa in un secondo piuttosto che in 10 minuti.

Scambiamo lo spazio occupato in eccesso e i dati denormalizzati con la possibilità di scalare bene e di funzionare in modo affidabile. Infatti, un cluster composto da tre datacenter, ognuno con cinque nodi, in grado di garantire un livello accettabile di integrità dei dati (dove niente va perso), può sopravvivere alla perdita totale di un datacenter. E ancora, ci sono due nodi in ciascuno dei due datacenter rimanenti. Solo dopo ciò iniziano i problemi. Questo è un buon livello di ridondanza, che richiede solo un paio di SSD extra e processori. Pertanto, per utilizzare Cassandra, che non è SQL e non prevede relazioni né chiavi esterne, è necessario conoscere alcune regole semplici.

Progettiamo tutto partendo dalla richiesta. Non sono i dati ad avere importanza, ma il modo in cui l'applicazione interagirà con essi. Se ha bisogno di recuperare dati diversi in modi diversi o gli stessi dati in modi differenti, dobbiamo organizzarli in modo che siano comodi per l'applicazione. Altrimenti, finiremo per eseguire un Full Scan e Cassandra non ci porterà alcun vantaggio.

Denormalizzare i dati è la norma. Dimentichiamo le forme normali, non abbiamo più database relazionali. Metteremo qualcosa 100 volte, e questo sarà presente 100 volte. È comunque più economico che rallentare.

Selezioniamo le chiavi per il partizionamento in modo che siano distribuite uniformemente. Non vogliamo che l'hash delle nostre chiavi cada in un intervallo ristretto. Ad esempio, l'anno di nascita nella precedente discussione è un cattivo esempio. In effetti, è un buon esempio se i nostri utenti sono distribuiti benissimo per anno di nascita, ed è un cattivo esempio se si tratta di alunni di quinta elementare—lì non sarà molto efficace il partizionamento.

La sortazione viene scelta una sola volta durante la creazione della Clustering Key. Se deve essere modificata, allora dovremo riversare la nostra tabella con un'altra chiave.

E la cosa più importante: se abbiamo bisogno di recuperare gli stessi dati in 100 modi diversi, avremo 100 tabelle diverse.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster