Fondamenti della progettazione di database: un confronto tra PostgreSQL, Cassandra e MongoDB

Ciao, amici. Prima di partire per la seconda parte delle vacanze di maggio, condividiamo con voi un materiale che abbiamo tradotto in preparazione al lancio del nuovo corso. «Database Relazionali».

Fondamenti della progettazione di database: un confronto tra PostgreSQL, Cassandra e MongoDB

Gli sviluppatori di applicazioni spendono molto tempo a confrontare diversi database operativi per scegliere quello più adatto al carico di lavoro previsto. Le esigenze possono includere una modellazione dei dati semplificata, garanzie transazionali, prestazioni di lettura/scrittura, scalabilità orizzontale e resilienza. Tradizionalmente, la scelta inizia con la categoria del database, SQL o NoSQL, poiché ogni categoria offre un set chiaro di compromessi. Prestazioni elevate in termini di bassa latenza e alta larghezza di banda vengono generalmente considerate una necessità non negoziabile, rendendole quindi essenziali per qualsiasi database selezionato.

L'obiettivo di questo articolo è aiutare gli sviluppatori di applicazioni a fare la scelta giusta tra SQL e NoSQL nel contesto della modellazione dei dati delle applicazioni. Esamineremo un database SQL, ossia PostgreSQL, e due database NoSQL: Cassandra e MongoDB, per discutere i fondamenti della progettazione di database, come la creazione di tabelle, il loro popolamento, la lettura dei dati dalle tabelle e la loro rimozione. Nel prossimo articolo esamineremo sicuramente indici, transazioni, JOIN, direttive TTL e progettazione di database basata su JSON.

Qual è la differenza tra SQL e NoSQL?

I database SQL aumentano la flessibilità dell'applicazione grazie alle garanzie transazionali ACID e alla loro capacità di interrogare i dati tramite JOIN in modi inaspettati sopra i modelli relazionali normalizzati esistenti.

Considerando la loro architettura monolitica/a nodi singoli e l'uso del modello di replicazione master-slave per la ridondanza, i tradizionali database SQL mancano di due importanti caratteristiche: scalabilità lineare in scrittura (ovvero la suddivisione automatica su più nodi) e perdita di dati automatica/zero. Ciò significa che la quantità di dati ricevuti non può superare la massima capacità di scrittura di un nodo. Inoltre, una certa perdita temporale di dati deve essere presa in considerazione in caso di fault tolerance (in un'architettura senza separazione delle risorse). Qui bisogna tenere a mente che gli ultimi commit non si riflettono ancora nella copia secondaria (slave). Le aggiornamenti senza downtime sono anche difficili da realizzare nei database SQL.

I database NoSQL sono per loro natura tipicamente distribuiti, il che significa che i dati sono suddivisi in sezioni e distribuiti su più nodi. Richiedono denormalizzazione. Ciò implica che i dati inseriti devono essere copiati più volte per rispondere a specifiche richieste che si inviano. L'obiettivo generale è ottenere alte prestazioni riducendo il numero di shard disponibili durante la lettura. Ne consegue che NoSQL richiede di modellare le proprie query, mentre SQL richiede di modellare i propri dati.

NoSQL si concentra sul raggiungimento di alte prestazioni in un cluster distribuito e questo è il principale giustificativo per molti compromessi nel design dei database, che includono la perdita delle garanzie di transazione ACID, i JOIN e gli indici secondari globali coerenti.

C'è un'opinione secondo cui, sebbene i database NoSQL forniscano scalabilità lineare in scrittura e alta tolleranza ai guasti, la perdita delle garanzie transazionali li rende inadatti per i dati critici.

La tabella seguente mostra come la modellazione dei dati in NoSQL differisca da SQL.

Fondamenti della progettazione di database: un confronto tra PostgreSQL, Cassandra e MongoDB

SQL e NoSQL: Perché è necessario entrambi?

Sulle applicazioni reali con un gran numero di utenti, come Amazon.com, Netflix, Uber e Airbnb, ricade l'esecuzione di compiti complessi e diversificati. Ad esempio, un'applicazione di e-commerce simile a Amazon.com deve gestire dati leggeri e altamente critici, come informazioni su utenti, prodotti, ordini e fatture, insieme a dati più pesanti, ma meno sensibili, come recensioni di prodotti, messaggi del servizio clienti, attività degli utenti, feedback e raccomandazioni. Naturalmente, queste applicazioni si basano almeno su un database SQL, insieme a almeno un database NoSQL. Nelle sistemi interregionali e globali, un database NoSQL funge da cache geodistribuita per i dati memorizzati in una fonte fidata, un database SQL operante in una specifica regione.

Come unisce YugaByte DB SQL e NoSQL?

Costruita su un motore ibrido orientato ai log per lo storage, auto-sharding, replica di consenso distribuita basata su sharding e transazioni distribuite ACID (ispirate a Google Spanner), YugaByte DB è il primo database open-source al mondo compatibile sia con NoSQL (Cassandra & Redis) sia con SQL (PostgreSQL). Come mostrato nella tabella qui sotto, YCQL, l'API di YugaByte DB compatibile con Cassandra, aggiunge concetti di transazioni ACID a chiave singola e multipla e indici secondari globali all'API NoSQL, aprendo così un'era di database NoSQL transazionali. Inoltre, YCQL, l'API di YugaByte DB compatibile con PostgreSQL, integra concetti di scalabilità lineare della scrittura e resilienza automatica nell'API SQL, presentando al mondo database SQL distribuiti. Poiché il database YugaByte DB è intrinsecamente transazionale, ora l'API NoSQL può essere utilizzata nel contesto di dati critici.

Fondamenti della progettazione di database: un confronto tra PostgreSQL, Cassandra e MongoDB

Come già menzionato nell'articolo "Introducing YSQL: A PostgreSQL Compatible Distributed SQL API for YugaByte DB", la scelta tra SQL o NoSQL in YugaByte DB dipende completamente dalle caratteristiche del carico di lavoro principale:

  • Se il carico di lavoro principale consiste in operazioni multiple con JOIN, quando si sceglie YSQL, è importante considerare che le vostre chiavi possono essere distribuite su più nodi, il che comporterà una latenza più elevata e/o una diminuzione della larghezza di banda rispetto a NoSQL.
  • In caso contrario, scegli uno dei due API NoSQL, tenendo presente che otterrai migliori prestazioni da richieste gestite da un singolo nodo alla volta. YugaByte DB può fungere da unico database operativo per applicazioni complesse reali in cui è necessario gestire più carichi di lavoro contemporaneamente.

Alla base del laboratorio di modellazione dei dati (Data modeling lab) nella seguente sezione ci sono database YugaByte DB compatibili con PostgreSQL e Cassandra, a differenza dei database di origine. Questo approccio sottolinea la semplicità di interazione con due API diverse (su due porte diverse) dello stesso cluster di database, rispetto all'uso di cluster completamente indipendenti di due database distinti.
Nelle prossime sezioni ci familiarizzeremo con il laboratorio di modellazione dei dati per illustrare le differenze e alcune somiglianze tra i database considerati.

Laboratorio di modellazione dei dati

Installazione dei database

Tenendo presente l'accento sulla progettazione del modello di dati (e non su architetture di distribuzione complesse), installeremo i database in contenitori Docker sul computer locale e poi interagirà con essi utilizzando le rispettive shell della riga di comando.

Database YugaByte DB compatibile con PostgreSQL & Cassandra

mkdir ~/yugabyte && cd ~/yugabyte
wget https://downloads.yugabyte.com/yb-docker-ctl && chmod +x yb-docker-ctl
docker pull yugabytedb/yugabyte
./yb-docker-ctl create --enable_postgres

MongoDB

docker run --name my-mongo -d mongo:latest

Accesso tramite riga di comando

Colleghiamoci ai database utilizzando la shell della riga di comando per le rispettive API.

PostgreSQL

psql è la shell della riga di comando per interagire con PostgreSQL. Per facilitare l'uso, YugaByte DB viene fornito con psql direttamente nella cartella bin.

docker exec -it yb-postgres-n1 /home/yugabyte/postgres/bin/psql -p 5433 -U postgres

Cassandra

cqlsh è la shell della riga di comando per interagire con Cassandra e i suoi database compatibili tramite CQL (Cassandra Query Language). Per comodità, YugaByte DB viene fornito con cqlsh nella directory bin.
Nota che CQL è stato ispirato da SQL e ha concetti analoghi di tabelle, righe, colonne e indici. Tuttavia, essendo un linguaggio NoSQL, aggiunge un set di vincoli specifici, la maggior parte dei quali tratteremo anche in altri articoli.

docker exec -it yb-tserver-n1 /home/yugabyte/bin/cqlsh

MongoDB

mongo – è una shell della riga di comando per interagire con MongoDB. Può essere trovata nella cartella bin dell'installazione di MongoDB.

docker exec -it my-mongo bash 
cd bin
mongo

Creazione della tabella

Ora possiamo interagire con il database per eseguire diverse operazioni tramite la riga di comando. Iniziamo con la creazione di una tabella che memorizza informazioni sulle canzoni scritte da vari artisti. Queste canzoni possono far parte di un album. Gli attributi opzionali per una canzone sono l'anno di pubblicazione, il prezzo, il genere e la valutazione. Dobbiamo considerare attributi aggiuntivi che potrebbero essere necessari in futuro, attraverso il campo "tag". Questo può memorizzare dati semi-strutturati in forma di coppie chiave-valore.

PostgreSQL

CREATE TABLE Music (
    Artist VARCHAR(20) NOT NULL, 
    SongTitle VARCHAR(30) NOT NULL,
    AlbumTitle VARCHAR(25),
    Year INT,
    Price FLOAT,
    Genre VARCHAR(10),
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);	

Cassandra

La creazione di una tabella in Cassandra è molto simile a PostgreSQL. Una delle principali differenze è l'assenza di vincoli di integrità (ad esempio, NOT NULL), ma questa è responsabilità dell'applicazione, e non del database NoSQL.. La chiave primaria è composta dalla chiave di partizione (la colonna Artist nell'esempio qui sotto) e da un insieme di colonne di clustering (la colonna SongTitle nell'esempio qui sotto). La chiave di partizione determina in quale partizione/shard collocare la riga, mentre le colonne di clustering indicano come devono essere organizzati i dati all'interno dello shard attuale.

CREATE KEYSPACE myapp;
USE myapp;
CREATE TABLE Music (
    Artist TEXT, 
    SongTitle TEXT,
    AlbumTitle TEXT,
    Year INT,
    Price FLOAT,
    Genre TEXT,
    CriticRating FLOAT,
    Tags TEXT,
    PRIMARY KEY(Artist, SongTitle)
);

MongoDB

MongoDB organizza i dati in database (Database) (analogo a Keyspace in Cassandra), dove ci sono collezioni (Collections) (analogo a tabelle), contenenti documenti (Documents) (analogo a righe nella tabella). In MongoDB, non è necessario definire uno schema iniziale. Il comando "use database", mostrato di seguito, crea un'istanza del database alla prima chiamata e cambia il contesto per il database appena creato. Anche le collezioni non devono essere create esplicitamente, vengono create automaticamente semplicemente aggiungendo il primo documento a una nuova collezione. Si noti che MongoDB utilizza per impostazione predefinita un database di test, quindi qualsiasi operazione a livello di collezioni senza specificare un database specifico verrà eseguita su di esso per impostazione predefinita.

use myNewDatabase;

Ottenere informazioni sulla tabella
PostgreSQL

d Musica
Tabella "public.music"
    Colonna    |         Tipo          | Ordinamento | Nullabile | Predefinito 
--------------+-----------------------+-----------+----------+--------
 artista      | carattere variabile(20) |           | non nullo | 
 titolo canzone | carattere variabile(30) |           | non nullo | 
 titolo album   | carattere variabile(25) |           |          | 
 anno          | intero               |           |          | 
 prezzo        | precisione doppia     |           |          | 
 genere        | carattere variabile(10) |           |          | 
 valutazione critico | precisione doppia     |           |          | 
 tags          | testo                  |           |          | 
Indici:
    "music_pkey" CHIAVE PRIMARIA, btree (artista, titolo canzone)

Cassandra

DESCRIVI TABELLA MUSICA;
CREA TABELLA myapp.music (
    artista testo,
    titolo canzone testo,
    titolo album testo,
    anno int,
    prezzo float,
    genere testo,
    tags testo,
    CHIAVE PRIMARIA (artista, titolo canzone)
) CON ORDINE DI CLUSTERING PER (titolo canzone ASC)
    E default_time_to_live = 0
    E transazioni = {'enabled': 'false'};

MongoDB

usa myNewDatabase;
mostra collezioni;

Inserimento dati nella tabella
PostgreSQL

INSERISCI IN Music 
    (Artista, TitoloCanzone, TitoloAlbum, 
    Anno, Prezzo, Genere, ValutazioneCritico, 
    Tags)
VALORI(
    'No One You Know', 'Call Me Today', 'Somewhat Famous',
    2015, 2.14, 'Country', 7.8,
    '{"Composers": ["Smith", "Jones", "Davis"],"LengthInSeconds": 214}'
);
INSERISCI IN Music 
    (Artista, TitoloCanzone, TitoloAlbum, 
    Prezzo, Genere, ValutazioneCritico)
VALORI(
    'No One You Know', 'My Dog Spot', 'Hey Now',
    1.98, 'Country', 8.4
);
INSERISCI IN Music 
    (Artista, TitoloCanzone, TitoloAlbum, 
    Prezzo, Genere)
VALORI(
    'The Acme Band', 'Look Out, World', 'The Buck Starts Here',
    0.99, 'Rock'
);
INSERISCI IN Music 
    (Artista, TitoloCanzone, TitoloAlbum, 
    Prezzo, Genere, 
    Tags)
VALORI(
    'The Acme Band', 'Still In Love', 'The Buck Starts Here',
    2.47, 'Rock', 
    '{"radioStationsPlaying": ["KHCR", "KBQX", "WTNR", "WJJH"], "tourDates": { "Seattle": "20150625", "Cleveland": "20150630"}, "rotation": Heavy}'
);

Cassandra

In generale, l'espressione INSERISCI in Cassandra appare molto simile a quella in PostgreSQL. Tuttavia c'è una grande differenza nella semantica. In Cassandra INSERISCI è di fatto un'operazione UPSERT, dove nella riga vengono aggiunti i valori più recenti, nel caso in cui la riga esista già.

L'immissione dei dati avviene in modo simile a PostgreSQL INSERISCI sopra

.

MongoDB

Nonostante MongoDB sia un database NoSQL, simile a Cassandra, la sua operazione di immissione dati non ha nulla a che fare con il comportamento semantico in Cassandra. In MongoDB insert() non ha funzionalità UPSERT, il che lo rende simile a PostgreSQL. L'aggiunta di dati senza specificare _id porterà all'aggiunta di un nuovo documento nella collezione. Richiesta della tabella

db.music.insert( {
artista: "No One You Know",
titoloCanzone: "Chiamami Oggi",
titoloAlbum: "Un po' Famoso",
anno: 2015,
prezzo: 2.14,
genere: "Country",
tags: {
Compositori: ["Smith", "Jones", "Davis"],
DurataInSecondi: 214
}
}
);
db.music.insert( {
artista: "No One You Know",
titoloCanzone: "Il Mio Cane Spot",
titoloAlbum: "Hey Now",
prezzo: 1.98,
genere: "Country",
valutazioneCritica: 8.4
}
);
db.music.insert( {
artista: "The Acme Band",
titoloCanzone: "Guarda Fuori, Mondo",
titoloAlbum:"The Buck Starts Here",
prezzo: 0.99,
genere: "Rock"
}
);
db.music.insert( {
artista: "The Acme Band",
titoloCanzone: "Ancora In Amore",
titoloAlbum:"The Buck Starts Here",
prezzo: 2.47,
genere: "Rock",
tags: {
radioStazioniInRiproduzione:["KHCR", "KBQX", "WTNR", "WJJH"],
dateTour: {
Seattle: "20150625",
Cleveland: "20150630"
},
rotazione: "Pesante"
}
}
);

Potrebbe esserci una differenza sostanziale tra SQL e NoSQL dal punto di vista della formulazione delle richieste, che risiede nell'uso delle formulazioni

DA DA e DOVEdi selezionare più tabelle, mentre l'espressione con DA può avere qualsiasi complessità (inclusa l'operazione DOVE JOIN JOIN , e lavora solo con una tabella specificata, mentre DA, e lavora solo con una tabella specificata. DOVE, deve sempre essere specificata una chiave primaria. Questo è legato al desiderio di migliorare le prestazioni di NoSQL di cui abbiamo parlato in precedenza. Questo desiderio porta a ridurre al minimo qualsiasi interazione tra tabelle incrociate e chiavi incrociate. Può causare un'elevata latenza nella comunicazione tra nodi quando si risposta a una query e, di conseguenza, è meglio evitarlo in linea di principio. Ad esempio, Cassandra richiede che le query siano limitate a determinati operatori (sono permessi solo =, IN, , =>, <=) sulle chiavi delle partizioni, ad eccezione dei casi di query su indici secondari (qui è permesso solo l'operatore =).

PostgreSQL

Di seguito sono riportati tre esempi di query che possono essere eseguite facilmente in un database SQL.

  • Elenca tutte le canzoni di un artista;
  • Elenca tutte le canzoni di un artista che corrispondono alla prima parte del titolo;
  • Elenca tutte le canzoni di un artista che contengono una certa parola nel titolo e hanno un prezzo inferiore a 1.00.
SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE 'Call%';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle LIKE '%Today%'
AND Price > 1.00;

Cassandra

Tra le query sopra elencate, solo la prima funzionerà in Cassandra senza modifiche, poiché l'operatore LIKE non può essere applicato alle colonne di clustering, come SongTitle. In questo caso, sono consentiti solo gli operatori = e IN.

SELECT * FROM Music
WHERE Artist='No One You Know';
SELECT * FROM Music
WHERE Artist='No One You Know' AND SongTitle IN ('Call Me Today', 'My Dog Spot')
AND Price > 1.00;

MongoDB

Come mostrato negli esempi precedenti, il metodo principale per creare query in MongoDB è db.collection.find(). Questo metodo contiene esplicitamente il nome della collezione (music nell'esempio seguente), quindi la query su più collezioni non è consentita.

db.music.find( {
  artist: "No One You Know"
 } 
);
db.music.find( {
  artist: "No One You Know",
  songTitle: /Call/
 } 
);

Lettura di tutte le righe della tabella

La lettura di tutte le righe è semplicemente un caso particolare di quel modello di query che abbiamo esaminato in precedenza.

PostgreSQL

SELECT * 
FROM Music;

Cassandra

Simile all'esempio in PostgreSQL sopra.

MongoDB

db.music.find( {} );

Modifica dei dati nella tabella

PostgreSQL

PostgreSQL fornisce l'istruzione UPDATE per modificare i dati. Essa non ha capacità UPSERT, quindi l'esecuzione di questa istruzione genererà un errore se le righe non sono più presenti nel database.

UPDATE Music
SET Genre = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';

Cassandra

In Cassandra c'è UPDATE un equivalente di PostgreSQL. UPDATE ha la stessa semantica UPSERT, simile a INSERISCI.

Simile all'esempio in PostgreSQL sopra.

MongoDB
Operazione update() In MongoDB è possibile aggiornare completamente un documento esistente o aggiornare solo determinati campi. Per impostazione predefinita, aggiorna solo un documento con la semantica disabilitata. UPSERT. L'aggiornamento di più documenti e il comportamento sono analoghi UPSERT e possono essere applicati impostando flag aggiuntivi per l'operazione. Come nell'esempio seguente, avviene l'aggiornamento del genere di un artista specifico in base alla sua canzone.

db.music.update(
  {"artist": "The Acme Band"},
  { 
    $set: {
      "genre": "Disco"
    }
  },
  {"multi": true, "upsert": true}
);

Eliminazione dei dati da una tabella

PostgreSQL

DELETE FROM Music
WHERE Artist = 'The Acme Band' AND SongTitle = 'Look Out, World';

Cassandra

Simile all'esempio in PostgreSQL sopra.

MongoDB

In MongoDB ci sono due tipi di operazioni per eliminare documenti — deleteOne() /deleteMany() e remove(). Entrambi i tipi eliminano documenti, ma restituiscono risultati diversi.

db.music.deleteMany( {
        artist: "The Acme Band"
    }
);

Eliminazione di una tabella

PostgreSQL

DROP TABLE Music;

Cassandra

Simile all'esempio in PostgreSQL sopra.

MongoDB

db.music.drop();

Conclusione

Le dispute sulla scelta tra SQL e NoSQL infuriano da oltre 10 anni. Ci sono due aspetti principali di questa disputa: l'architettura centrale del database (SQL monolitico e transazionale contro NoSQL distribuito e non transazionale) e l'approccio alla progettazione del database (modellazione dei dati in SQL contro modellazione delle tue query in NoSQL).

Con un database transazionale distribuito come YugaByte DB, i dibattiti riguardo all'architettura del database possono essere facilmente dissipati. Man mano che i volumi di dati superano ciò che può essere scritto in un singolo nodo, un'architettura completamente distribuita che supporta la scalabilità lineare della scrittura con partizionamento automatico/ri-bilanciamento diventa necessaria.

Oltre a quanto indicato in uno degli articoli Google Cloud, le architetture transazionali e rigorosamente coerenti sono ora più ampiamente utilizzate per garantire una maggiore flessibilità nello sviluppo rispetto a quelle non transazionali, coerentemente finali.

Tornando alla discussione sulla progettazione di database, è giusto dire che entrambi gli approcci progettuali (SQL e NoSQL) sono necessari per qualsiasi applicazione reale complessa. L'approccio SQL di "modellazione dei dati" consente agli sviluppatori di soddisfare più facilmente le mutevoli esigenze aziendali, mentre l'approccio NoSQL di "modellazione delle query" consente agli stessi sviluppatori di gestire grandi volumi di dati con bassa latenza e alta capacità. È per questo motivo che YugaByte DB offre API SQL e NoSQL in un unico nucleo, senza promuovere un approccio specifico. Inoltre, garantendo la compatibilità con i linguaggi di database più popolari, tra cui PostgreSQL e Cassandra, YugaByte DB assicura che gli sviluppatori non debbano apprendere un altro linguaggio per lavorare con il nucleo di database distribuito e fortemente coerente.

In questo articolo abbiamo esplorato come i principi di progettazione dei database differiscano in PostgreSQL, Cassandra e MongoDB. Negli articoli successivi ci tufferemo in concetti avanzati di progettazione come indici, transazioni, JOIN, direttive TTL e documenti JSON.

Vi auguriamo un ottimo fine settimana e vi invitiamo a un webinar gratuito, che si terrà il 14 maggio.

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