MongoDB 8.0 – Test di performance? Fatto e spiegato!

Introduzione

Dal 2009, MongoDB ha rivoluzionato il modo in cui vengono gestiti i dati, offrendo un’alternativa ai database relazionali tradizionali diventando negli anni uno standard di mercato ed una delle soluzioni più adottate per applicazioni moderne, dalle startup alle grandi aziende.

MongoDB è un database NoSQL orientato ai documenti, una caratteristica che lo distingue dalle tradizionali basi di dati relazionali. È particolarmente apprezzato per la sua flessibilità: i documenti non richiedono una struttura fissa, permettendo una gestione dei dati più dinamica e adattabile.

I documenti, strutturati secondo la sintassi JSON, sono formati da coppie chiave-valore. Il valore può essere un numero, una stringa, ma anche un array o un oggetto, offrendo così grande flessibilità nella rappresentazione dei dati.

I dati sono memorizzati in documenti BSON (una versione binaria e compressa di JSON), raggruppati in un oggetto chiamato Collection. Un database è composto da più collection, mentre una singola istanza può ospitare diversi database.

Per fare un esempio comparativo, immaginiamo di voler creare una base dati relativa alle automobili.

Con un database relazionale, potremmo strutturare una tabella principale (master) contenente le informazioni base del modello, come il nome, il codice colore, il codice motore, il codice del cambio, ecc. A ciascun codice corrisponderebbe una tabella dedicata (es. tabella dei motori, dei cambi, dei colori), collegata alla tabella principale tramite chiavi esterne (foreign key). Questo approccio richiede una forte normalizzazione e l’utilizzo di relazioni tra tabelle per poter ricostruire il modello completo.

Con un database non relazionale, invece, ogni automobile può essere rappresentata come un documento completo e autonomo, che include direttamente tutte le informazioni rilevanti (colore, motore, cambio, ecc.), senza la necessità di recuperarle da collezioni separate. Questo consente una lettura dei dati più semplice e flessibile, in particolare quando le relazioni tra gli elementi sono guidate dal contenuto piuttosto che da riferimenti strutturati.

Il potenziale di MongoDB

Esistono tre tipi di architettura in MongoDB. La più semplice è la modalità standalone, ovvero una singola istanza. Per garantire invece resilienza e alta disponibilità, MongoDB supporta la replica: un database attivo in lettura e scrittura viene affiancato da più repliche in standby. In caso di guasto del server principale, una delle repliche può subentrare automaticamente, assicurando la continuità operativa del sistema.

Ora, immaginiamo che un client voglia leggere dei dati. Mongod (il processo principale di MongoDB) riceve la richiesta e inizia a lavorare: i dati sono strutturati in indici B-Tree (ne parleremo dopo) e quello di default è _id; quindi, anche se non viene richiesto in modo esplicito, i dati vengono comunque scritti in questo indice. In assenza di altri indici adeguati, mongod utilizza l’indice _id per individuare i documenti corretti nel modo più rapido possibile. Se i documenti richiesti sono già presenti nella cache, vengono recuperati direttamente da lì; in caso contrario, mongod li carica dai file mappati in memoria. Una volta trovati, li restituisce al client.

Una delle caratteristiche più intelligenti di mongod è il modo in cui gestisce la memoria. Usa una cache interna per mantenere i documenti più frequentemente utilizzati pronti per l’uso, il che accelera notevolmente le operazioni di lettura. Per quanto riguarda i file di dati su disco, invece, MongoDB utilizza un sistema chiamato memory-mapped files: i file di dati su disco sono mappati direttamente in memoria, permettendo un accesso rapido ai dati. Questo è il segreto della sua velocità: l’utilizzo intelligente della memoria, se ovviamente associato ad una modellazione del dato corretta.

Se, invece, il client desidera scrivere dei dati, mongod aggiorna prima la cache in memoria e poi i file di dati su disco. Ogni operazione di scrittura viene poi registrata in un journal per garantire che niente vada perso, anche in caso di crash del sistema. Mongod utilizza blocchi a livello di database e collection per assicurarsi che più operazioni possano avvenire in parallelo senza interferire l’una con l’altra. Con il motore WiredTiger acquisito nel 2014, MongoDB utilizza un metodo chiamato MVCC (Multi-Version Concurrency Control) per garantire che le letture siano sempre consistenti, anche mentre avvengono scritture.

Se MongoDB è configurato per la replica, mongod si occupa di replicare i dati su più server, assicurando che, in caso di guasto di uno di essi, le repliche possano subentrare senza causare interruzioni.

Il terzo tipo di architettura è lo Sharded Cluster, nato per gestire grandi volumi di dati e traffico. MongoDB utilizza questo tipo di architettura, che distribuisce i dati su più server, o “shard”. Ogni shard contiene una porzione dei dati e lavora insieme agli altri shard per formare un unico database logico. Mongos è il processo di routing di MongoDB: riceve query dalle applicazioni e utilizza i metadati dal Config Replicaset (uno speciale replicaset che ospita i metadati relativi al posizionamento dei dati negli shard) per instradare le query a mongod sottostanti. Sebbene mongos riesca a rendere operative tutte le query in ambienti partizionati, la shard key, ovvero la chiave di partizione selezionata, può avere un profondo effetto sulle prestazioni delle query.

Se la query include una chiave di shard, mongos instraderà la query direttamente a un singolo shard garantendo prestazioni migliori. In caso contrario, inoltrerà la richiesta ai mongod ricevendo il resultset da ognuno prima di inviare la risposta all’applicazione. Le query “scatter/gather” possono risultare in operazioni a lunga durata.

Questo porta con sè alcune importanti considerazioni. Se il Data Modeling è una fase importantissima per sfruttare al meglio le immense potenzialità prestazionali di MongoDB, lo è ancor di più se si intende utilizzare lo Sharding. Un modello di dati mal progettato costringe gli sviluppatori a usare l’operatore $lookup, che permette di eseguire join tra più collezioni. Inoltre, potrebbe impedire l’utilizzo delle chiavi di shard nelle query, dando origine a un meccanismo scatter/gather che, peggiorando notevolmente le prestazioni, annullerebbe i benefici dello sharding.

L’innovazione spesso non sta solo nell’inventare qualcosa di radicalmente nuovo, ma nel rivedere criticamente i limiti esistenti e superarli con soluzioni intuitive ed efficaci. Il non poter utilizzare $lookup in collection shardate, poter scegliere un determinato Shard dove poter posizionare una collection così da evitare meccanismi di scatter/gather, e poter ospitare dati nel config Replica Set, rappresentano un vero e proprio uovo di Colombo.

MongoDB Server 8.0 introduce tutte queste (e molte altre) innovazioni, e le novità riguardanti lo sharding sono probabilmente passate inosservate.

Un esempio pratico

Analizziamo ora questo use case.

Enterprise Corporation opera nel settore assicurativo e gestisce un’enorme quantità di dati tramite un’applicazione basata su MongoDB. Per far fronte alla crescita prevista e garantire la disponibilità dei dati per almeno 15 anni, sia per esigenze di audit che per offrire agli operatori una storicità completa in un unico cruscotto, l’azienda ha scelto un Sharded Cluster con tre shard.

Durante la fase di progettazione, i DBA hanno scelto un modello di dati efficiente, ma con alcune limitazioni derivanti dall’impossibilità di utilizzare l’operatore $lookup nelle aggregazioni, poiché tutte le collezioni erano shardate. Questo ha portato a uno sbilanciamento delle dimensioni di tre collection principali: mentre il numero di posizioni assicurative e polizze è cresciuto significativamente, il numero di prodotti vendibili è rimasto pressoché invariato.

Per mantenere alte prestazioni, è stato necessario dimensionare le macchine in modo che la memoria fosse adeguata alle collection più grandi, facendo lievitare i costi. Con l’arrivo di MongoDB Server 8.0, lo scenario è cambiato radicalmente.

MongoDB 8.0 introduce una nuova funzionalità che consente di spostare collezioni tra shard senza doverle necessariamente shardare. Questo permette ai DBA della Enterprise Corporation di riposizionare i dati all’interno del cluster, concentrandosi ad esempio sulle tre collezioni più grandi o, al contrario, su quelle più leggere. In questo modo, è possibile ridefinire le risorse hardware al ribasso qualora uno o più shard si trovino a gestire un volume di dati ridotto. Inoltre, a partire dalla versione 8.0, anche i config server possono ospitare dati applicativi, oltre ai metadati del cluster, diventando a tutti gli effetti config shard. Questo consente di spostare le collezioni di configurazione dell’applicazione – solitamente poco utilizzate – alleggerendo ulteriormente il carico sugli shard principali e ottimizzando l’uso delle risorse a livello di cluster.

Leggendo le release notes della nuova versione MongoDB, i DBA di SORINT.lab si sono accorti che $lookup è ora supportato nelle transazioni anche quando coinvolge collezioni shardate. Questo sarà particolarmente utile nel caso in cui si renda necessario effettuare estrazioni di dati più complesse, ad esempio su richiesta dell’audit.

In conclusione, MongoDB si conferma una soluzione solida e flessibile per la gestione di grandi volumi di dati. L’insieme di prestazioni elevate, gestione ottimizzata della memoria e supporto avanzato per replica e sharding lo rende una scelta matura e affidabile anche per gli scenari più complessi.