Nozioni di base sulla modellazione dei dati - PostgreSQL vs. Cassandra vs. MongoDB

Gli sviluppatori di applicazioni di solito impiegano molto tempo a valutare più database operativi per trovare quello che si adatta meglio alle loro esigenze di carico di lavoro. Queste esigenze comprendono la modellazione dei dati semplificata, le garanzie transazionali, le prestazioni di lettura / scrittura, il ridimensionamento orizzontale e la tolleranza agli errori. Tradizionalmente, questa selezione inizia con le categorie di database SQL e NoSQL perché ogni categoria presenta un chiaro insieme di compromessi. Le alte prestazioni in termini di bassa latenza e alta produttività sono generalmente trattate come un requisito non compromettibile e quindi sono attese in qualsiasi database scelto.

Questo post ha lo scopo di aiutare gli sviluppatori di applicazioni a comprendere la scelta di SQL rispetto a NoSQL nel contesto delle esigenze di modellazione dei dati di un'applicazione. Utilizziamo un database SQL, ovvero PostgreSQL, e 2 database NoSQL, ovvero Cassandra e MongoDB, come esempi per spiegare le basi della modellazione dei dati come la creazione di tabelle, l'inserimento di dati, l'esecuzione di scansioni e l'eliminazione di dati. In un post successivo, tratteremo argomenti avanzati come indici, transazioni, join, direttive time-to-live (TTL) e modellazione dei dati dei documenti basata su JSON.

In che modo NoSQL differisce da SQL nella modellazione dei dati?

I database SQL aumentano l'agilità delle applicazioni attraverso le garanzie transazionali ACID e con la loro capacità di eseguire query sui dati utilizzando JOIN in modi imprevisti oltre ai modelli di dati relazionali normalizzati esistenti.

Data la loro architettura monolitica / a nodo singolo e l'uso di un modello di replica master-slave per la ridondanza, nei database SQL tradizionali mancano due importanti funzionalità: la scalabilità di scrittura lineare (ovvero lo sharding automatico su più nodi) e il failover automatico / zero dati. Ciò significa che i volumi di dati ingeriti non possono superare la velocità di scrittura massima di un singolo nodo. Inoltre, ci si dovrebbe aspettare una perdita temporanea di dati in caso di failover (su architetture di archiviazione con nulla condiviso) dato che i recenti commit non sarebbero ancora stati mostrati nella replica slave. Anche gli aggiornamenti a zero tempi di inattività sono molto difficili da ottenere nel mondo del database SQL.

I DB NoSQL sono generalmente distribuiti in natura in cui i dati vengono partizionati o suddivisi su più nodi. Richiedono la denormalizzazione, il che significa che anche i dati inseriti devono essere copiati più volte per soddisfare le domande specifiche che hai in mente. L'obiettivo generale è quello di estrarre alte prestazioni riducendo esplicitamente il numero di frammenti a cui si accede durante il tempo di lettura. Da qui l'affermazione secondo cui NoSQL richiede di modellare le query mentre SQL richiede di modellare i dati.

L'attenzione di NoSQL al raggiungimento di elevate prestazioni su un cluster distribuito è dichiarata come la logica principale per i compromessi di modellazione di più dati che includono la perdita di transazioni ACID, JOIN e consistenti indici secondari globali.

La percezione generale è che anche se i database NoSQL forniscono scalabilità lineare in scrittura e alta tolleranza agli errori, la perdita di garanzie transazionali li rende inadatti ai dati mission-critical.

La tabella seguente descrive in che modo la modellazione dei dati NoSQL differisce da quella di SQL.

SQL vs. NoSQL - Differenze di modellazione dei dati chiave

SQL e NoSQL: perché hai bisogno di entrambi?

La maggior parte delle applicazioni del mondo reale con esperienze utente coinvolgenti come Amazon.com, Netflix, Uber e Airbnb sono alimentate internamente da una complessa combinazione di più carichi di lavoro. Per esempio. un'applicazione di e-commerce come Amazon.com deve archiviare dati a basso volume e altamente critici come utenti, prodotti, ordini, fatture insieme a dati ad alto volume e meno critici quali recensioni di prodotti, messaggi di helpdesk, attività dell'utente, consigli dell'utente. Naturalmente, queste applicazioni si basano su almeno un database SQL insieme ad almeno un database NoSQL. Nelle distribuzioni multiregione e globali, il database NoSQL funge anche da cache geo-distribuita per i dati archiviati nella fonte della verità, il database SQL in esecuzione in una singola regione.

In che modo YugaByte DB unisce SQL e NoSQL su un core di database comune?

Basato su una combinazione unica di motore di archiviazione di merge strutturato in log, auto-sharding, replica del consenso distribuito per shard e transazioni ACID distribuite (ispirata a Google Spanner), YugaByte DB è il primo database open source al mondo che è sia NoSQL (Cassandra & Redis compatibile) e SQL (compatibile PostgreSQL) contemporaneamente. Come mostrato nella tabella seguente, YCQL, l'API compatibile Cassandra di YugaByte DB, aggiunge la nozione di transazioni ACID a chiave singola e multi-chiave e indici secondari globali alle API NoSQL, inaugurando così l'era del NoSQL transazionale. Inoltre, YSQL, l'API compatibile PostgreSQL di YugaByte DB, aggiunge le nozioni di ridimensionamento lineare della scrittura e tolleranza di errore automatica a un'API SQL, dando così vita al mondo di SQL distribuito. Poiché YugaByte DB è transazionale al centro, anche le API NoSQL possono ora essere utilizzate nel contesto di dati mission-critical.

YugaByte DB - SQL e NoSQL su un singolo core del database

Come precedentemente descritto in Introduzione a YSQL: un'API SQL distribuita compatibile PostgreSQL per YugaByte DB, la scelta di SQL vs NoSQL in YugaByte DB dipende interamente dalle caratteristiche del maggior carico di lavoro.

  • Se il carico di lavoro maggioritario è costituito da operazioni multi-chiave con JOINS, selezionare YSQL con la consapevolezza che le chiavi possono essere distribuite su più nodi, portando a una latenza più elevata e / o a un throughput inferiore rispetto a NoSQL.
  • Altrimenti, scegli una delle due API NoSQL con la consapevolezza che otterrai vantaggi di prestazioni più elevati derivanti dalle query che vengono principalmente servite da un nodo alla volta. YugaByte DB può fungere da database operativo unificato per app del mondo reale complesse che di solito hanno più carichi di lavoro da gestire contemporaneamente.

Il laboratorio di modellizzazione dei dati nella sezione successiva si basa sulle API compatibili PostgreSQL e Cassandra di YugaByte DB rispetto ai database originali. Questo approccio evidenzia la semplicità dell'interazione con due diverse API (su due porte diverse) dello stesso cluster di database rispetto all'utilizzo di cluster completamente indipendenti di due database diversi.

Nelle prossime sezioni esamineremo un laboratorio pratico di modellazione dei dati per illustrare molte delle differenze e alcuni aspetti comuni tra database diversi.

Data Modeling Lab

Installa database

Dato il focus sulla modellazione dei dati (e non su architetture di distribuzione complesse), installeremo i database nei contenitori Docker sui nostri computer locali e quindi interagiremo con essi utilizzando le rispettive shell della riga di comando.

YugaByte DB, un database compatibile PostgreSQL e 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: ultimo

Accesso tramite Command Line Shell

Ora connettiamoci ai database usando le shell della riga di comando per le rispettive API.

PostgreSQL

psql è una shell della riga di comando per interagire con PostgreSQL. Per facilità d'uso, YugaByte DB viene fornito con una versione di psql nella sua directory bin.

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

cassandra

cqlsh è una shell della riga di comando per interagire con Cassandra e i suoi database compatibili tramite CQL (Cassandra Query Language). Per facilità d'uso, YugaByte DB viene fornito con una versione di cqlsh nella sua directory bin.

Si noti che CQL è fortemente ispirato a SQL con una nozione simile di tabelle, righe, colonne e indici. Tuttavia, come linguaggio NoSQL, aggiunge un insieme specifico di restrizioni, la maggior parte delle quali esamineremo durante le nostre serie di blog.

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

MongoDB

mongo è una shell della riga di comando per interagire con MongoDB. Può essere trovato nella directory bin di un'installazione MongoDB.

docker exec -it my-mongo bash
cd bin
mongo

Crea una tabella

Ora possiamo interagire con il database per varie operazioni usando la shell della riga di comando. Cominciamo con la creazione di una tabella che memorizza informazioni sui brani pubblicati da artisti. Queste canzoni fanno talvolta parte di un album. Gli altri attributi opzionali di una canzone sono l'anno, il prezzo, il genere e la valutazione critica. Abbiamo bisogno di tenere conto degli attributi aggiuntivi di cui potremmo avere bisogno in futuro attraverso un campo "tag" in grado di memorizzare dati semi-strutturati come coppie chiave-valore.

PostgreSQL

CREATE TABLE Music (
    Artista VARCHAR (20) NOT NULL,
    SongTitle VARCHAR (30) NOT NULL,
    AlbumTitle VARCHAR (25),
    Anno INT,
    Prezzo GALLEGGIANTE,
    Genere VARCHAR (10),
    CriticRating FLOAT,
    Tag TEXT,
    PRIMARY KEY (Artist, SongTitle)
);

cassandra

Creare una tabella in Cassandra è molto simile a quello di PostgreSQL. Una grande differenza è la mancanza di vincoli di integrità (come NOT NULL) che è la responsabilità dell'applicazione e non del database nel mondo NoSQL. La chiave primaria comprende la chiave di partizione (la colonna Artista nell'esempio seguente) e un insieme di colonne di raggruppamento (la colonna SongTitle nell'esempio seguente). La chiave di partizione determina in quale partizione / shard posizionare la riga e le colonne del cluster specificano come devono essere organizzati i dati all'interno di un dato shard.

CREA KEYSPACE myapp;
USA myapp;
CREATE TABLE Music (
    Artista TEXT,
    SongTitle TEXT,
    AlbumTitle TEXT,
    Anno INT,
    Prezzo GALLEGGIANTE,
    Genere TEXT,
    CriticRating FLOAT,
    Tag TEXT,
    PRIMARY KEY (Artist, SongTitle)
);

MongoDB

MongoDB organizza i dati in database (equivalenti a Cassandra Keyspace) con raccolte (equivalenti a tabelle) con documenti (equivalenti a una riga in una tabella). Come database "schemaless", la definizione dello schema in anticipo non è necessaria in MongoDB. Il comando "usa database" mostrato di seguito crea un'istanza di un database la prima volta che viene chiamato insieme al cambio di contesto nel database appena creato. Anche le raccolte non devono essere create esplicitamente, ma vengono create automaticamente inserendo semplicemente il primo documento in una nuova raccolta. Si noti che il database predefinito di MongoDB viene testato, quindi qualsiasi operazione a livello di raccolta eseguita senza specificare il database verrà eseguita in questo contesto predefinito.

usa myNewDatabase;

Ottieni informazioni su una tabella

PostgreSQL

\ d Musica
Tabella "public.music"
    Colonna | Digita | Collazione | Nullable | Predefinito
-------------- + ----------------------- + ----------- + ---------- + --------
 artista | carattere variabile (20) | | non nullo |
 songtitle | carattere variabile (30) | | non nullo |
 albumtitle | carattere variabile (25) | | |
 anno | intero | | |
 prezzo | doppia precisione | | |
 genere | carattere variabile (10) | | |
 criticare | doppia precisione | | |
 tag | testo | | |
indici:
    "music_pkey" PRIMARY KEY, btree (artista, songtitle)

cassandra

DESCRIVERE TABELLA MUSICA;
CREATE TABLE myapp.music (
    testo dell'artista,
    testo del brano,
    testo dell'albumtitle,
    anno int,
    galleggiante di prezzo,
    testo di genere,
    tag testo,
    CHIAVE PRIMARIA (artista, canzone)
) CON CLUSTERING ORDER BY (songtitle ASC)
    AND default_time_to_live = 0
    AND transazioni = {'enabled': 'false'};

MongoDB

usa myNewDatabase;
mostra collezioni;

Inserisci i dati in una tabella

PostgreSQL

INSERIRE IN Musica
    (Artista, SongTitle, AlbumTitle,
    Anno, prezzo, genere, valutazione critica,
    tag)
VALORI(
    "Nessuno che conosci", "Chiamami oggi", "Un po 'famoso",
    2015, 2,14, "Paese", 7,8,
    '{"Compositori": ["Smith", "Jones", "Davis"], "LengthInSeconds": 214}'
);
INSERIRE IN Musica
    (Artista, SongTitle, AlbumTitle,
    Prezzo, genere, valutazione critica)
VALORI(
    'No One You Know', 'My Dog Spot', 'Hey Now',
    1.98, "Paese", 8.4
);
INSERIRE IN Musica
    (Artista, SongTitle, AlbumTitle,
    Prezzo, Genere)
VALORI(
    "The Acme Band", "Look Out, World", "The Buck Starts Here",
    0,99, 'Rock'
);
INSERIRE IN Musica
    (Artista, SongTitle, AlbumTitle,
    Prezzo, Genere,
    tag)
VALORI(
    "The Acme Band", "Still In Love", "The Buck Starts Here",
    2.47, "Rock",
    '{"radioStationsPlaying": ["KHCR", "KBQX", "WTNR", "WJJH"], "tourDates": {"Seattle": "20150625", "Cleveland": "20150630"}, "rotazione": Pesante}'
);

cassandra

Le istruzioni Cassandra INSERT sembrano molto simili a quelle di PostgreSQL in generale. Tuttavia, c'è una grande differenza nella semantica. INSERT è in realtà un'operazione di upsert in Cassandra in cui la riga viene aggiornata con gli ultimi valori nel caso in cui la riga esista già.

Come le precedenti istruzioni INSERT di PostgreSQL.

MongoDB

Anche se MongoDB è anche un database NoSQL simile a Cassandra, la sua operazione di inserimento non ha lo stesso comportamento semantico di Cassandra. MongoDB insert () non ha possibilità di upsert che lo rende simile a PostgreSQL. Il comportamento di inserimento predefinito senza _idspecified porterà a un nuovo documento aggiunto alla raccolta.

db.music.insert ({
artista: "Nessuno che conosci",
   songTitle: "Call Me Today",
    albumTitle: "Somewhat Famous",
    anno: 2015,
    prezzo: 2.14,
    genere: "Paese",
    tag: {
Compositori: ["Smith", "Jones", "Davis"],
LunghezzaIn secondi: 214
}
   }
);
db.music.insert ({
    artista: "Nessuno che conosci",
    songTitle: "My Dog Spot",
    albumTitle: "Hey Now",
    prezzo: 1.98,
    genere: "Paese",
    critica: 8.4
   }
);
db.music.insert ({
    artista: "The Acme Band",
    songTitle: "Look Out, World",
    albumTitle: "The Buck Starts Here",
    prezzo: 0.99,
    genere: "Rock"
   }
);
db.music.insert ({
    artista: "The Acme Band",
    songTitle: "Still In Love",
    albumTitle: "The Buck Starts Here",
    prezzo: 2.47,
    genere: "Rock",
    tag: {
        radioStationsPlaying: ["KHCR", "KBQX", "WTNR", "WJJH"],
        date del tour: {
            Seattle: "20150625",
            Cleveland: "20150630"
        },
        rotazione: "pesante"
}
    }
);

Interroga una tabella

Probabilmente la differenza più significativa tra SQL e NoSQL in termini di query di modellazione è sull'uso delle clausole FROM e WHERE. SQL consente alla clausola FROM di includere più tabelle e la clausola WHERE di complessità arbitraria (inclusi JOIN su più tabelle). Tuttavia, NoSQL tende a porre una forte limitazione alla clausola FROM per avere solo una tabella specificata e la clausola WHERE per avere sempre la chiave primaria specificata. Ciò è dovuto al focus sulle alte prestazioni di NoSQL che abbiamo discusso in precedenza che mira a ridurre qualsiasi interazione tra tabelle incrociate e chiavi incrociate. Tale interazione può introdurre una comunicazione tra nodi ad alta latenza nel tempo di risposta alla query e quindi è meglio evitarla del tutto. Per esempio. Cassandra richiede che le query siano limitate dagli operatori (sono consentiti solo =, IN, <,>, =>, <=) sulle chiavi di partizione, tranne quando si esegue una query su un indice secondario (dove è consentito solo l'operatore).

PostgreSQL

Di seguito sono riportati 3 tipi di query che possono essere servite facilmente da un database SQL.

  • Restituisce tutte le canzoni di un artista
  • Restituisce tutte le canzoni di un artista, abbinando la prima parte del titolo
  • Restituisce tutte le canzoni di un artista, con una parola particolare nel titolo, ma solo se il prezzo è inferiore a 1,00
SELEZIONA * DA Musica
WHERE Artist = 'Nessuno che conosci';
SELEZIONA * DA Musica
WHERE Artist = 'Nessuno che conosci' E SongTitle COME 'Call%';
SELEZIONA * DA Musica
WHERE Artist = 'Nessuno che conosci' E SongTitle COME '% Today%'
E prezzo> 1,00;

cassandra

Delle query PostgreSQL sopra elencate, solo la prima funzionerà con Cassandra non modificata poiché l'operatore LIKE non è autorizzato su colonne di clustering come SongTitle. In questo caso sono consentiti solo operatori = e IN.

SELEZIONA * DA Musica
WHERE Artist = 'Nessuno che conosci';
SELEZIONA * DA Musica
WHERE Artist = 'No One You Know' E SongTitle IN ('Call Me Today', 'My Dog Spot')
E prezzo> 1,00;

MongoDB

Come mostrato negli esempi precedenti, il metodo principale per interrogare MongoDB è il metodo db.collection.find (). Questo metodo è qualificato dal nome della raccolta (musica nell'esempio seguente) per essere interrogato in modo molto esplicito, pertanto è esplicitamente vietato eseguire query tra raccolte.

db.music.find ({
  artista: "Nessuno che conosci"
 }
);
db.music.find ({
  artista: "Nessuno che conosci",
  songTitle: / Chiama /
 }
);

Leggi tutte le righe da una tabella

La lettura di tutte le righe è semplicemente un caso speciale del modello di query generico che abbiamo osservato in precedenza.

PostgreSQL

SELEZIONA *
FROM Music;

cassandra

Come l'istruzione SELECT PostgreSQL sopra.

MongoDB

db.music.find ({});

Modifica i dati in una tabella

PostgreSQL

PostgreSQL fornisce l'istruzione UPDATE per la modifica dei dati. Non consente alcuna possibilità di upsert, quindi l'istruzione fallirà se la riga non esiste già nel database.

AGGIORNA Musica
Genere SET = 'Disco'
WHERE Artist = 'The Acme Band' AND SongTitle = 'Still In Love';

cassandra

Cassandra ha anche un'istruzione UPDATE simile a PostgreSQL. AGGIORNAMENTO anche la stessa semantica upsert di quella dell'istruzione INSERT.

Come l'istruzione UPDATE PostgreSQL sopra.

MongoDB

L'operazione update () di MongoDB può aggiornare completamente un documento esistente o può aggiornare solo campi specifici. Per impostazione predefinita, aggiorna solo un documento con la semantica upsert disattivata. Gli aggiornamenti di più documenti e il comportamento di inversione possono essere attivati ​​impostando flag aggiuntivi sull'operazione. Per esempio. l'esempio seguente aggiorna il genere di un artista specifico in tutte le canzoni dell'artista.

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

Elimina dati da una tabella

PostgreSQL

ELIMINA DA Musica
WHERE Artist = 'The Acme Band' AND SongTitle = 'Look Out, World';

cassandra

Come l'istruzione DELETE PostgreSQL sopra.

MongoDB

MongoDB ha due tipi di operazioni per gestire le cancellazioni dei documenti: deleteOne () / deleteMany () e remove (). Entrambi eliminano i documenti ma hanno risultati di restituzione diversi.

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

Rimuovi una tabella

PostgreSQL

DROP TABLE Music;

cassandra

Come l'istruzione PostgreSQL DROP TABLE sopra;

MongoDB

db.music.drop ();

Sommario

Il dibattito tra SQL e NoSQL è in corso da oltre un decennio. Ci sono 2 aspetti di questo dibattito: l'architettura di base del database (monolitico, SQL transazionale vs NoSQL distribuito, non transazionale) e l'approccio alla modellazione dei dati (modella i tuoi dati in SQL vs. modella le tue query in NoSQL).

Con un database distribuito e transazionale come YugaByte DB, l'architettura del database parte del dibattito può essere messa a riposo facilmente. Man mano che i volumi di dati vanno oltre ciò che può essere scritto in un singolo nodo, un'architettura completamente distribuita che consente la scalabilità di scrittura lineare con il sharding / ribilanciamento automatico diventa un must. Inoltre, come descritto in questo post da Google Cloud, le architetture transazionali e fortemente coerenti sono ora ampiamente accettate per offrire maggiore agilità per sviluppatori e operazioni rispetto alle architetture non transazionali e infine coerenti.

Venendo al dibattito sulla modellizzazione dei dati, è corretto affermare che gli approcci alla modellazione dei dati SQL e NoSQL sono essenziali per qualsiasi applicazione complessa nel mondo reale. L'approccio basato sul modello SQL-your-data consente agli sviluppatori di soddisfare più facilmente le mutevoli esigenze aziendali, mentre l'approccio model-your-query di NoSQL consente agli stessi sviluppatori di gestire grandi volumi di dati con bassa latenza e throughput elevato. Questo è precisamente il motivo per cui YugaByte DB implementa le API SQL e NoSQL sul core comune invece di promuovere che un approccio sia strettamente migliore dell'altro. Inoltre, assicurando la compatibilità via cavo con i linguaggi di database più diffusi, compresi PostgreSQL e Cassandra, YugaByte DB garantisce che gli sviluppatori non abbiano imparato un'altra lingua per beneficiare del core del database distribuito e altamente coerente.

Questo post ci ha aiutato a capire in che modo le basi della modellazione dei dati differiscono tra PostgreSQL, Cassandra e MongoDB. Nei prossimi post della serie, approfondiremo concetti avanzati di modellazione dei dati come indici, transazioni, JOIN, direttive TTL e documenti JSON.

Qual'è il prossimo?

  • Confronta YugaByte DB con database come Amazon DynamoDB, Cassandra, MongoDB e Azure Cosmos DB.
  • Inizia con YugaByte DB su macOS, Linux, Docker e Kubernetes.
  • Contattaci per saperne di più su licenze, prezzi o per pianificare una panoramica tecnica.

Originariamente pubblicato sul blog del database YugaByte.