Primi passi con #MongoDB

MongoDB è uno database appartente alla nuova tipologia “NoSQL”, particolarmente adatti per le applicazioni Cloud. Lo scopo di MongoDB è quello di essere facilmente scalabile, orientato ai documenti ed estremamente veloce.
Per MongoDB, difatti, tutte le informazini memorizzate sono dei documenti in formato JSON e, per memorizzarle, utilizza uno storage basato su GridFS che quindi è per definizione scalabile orizzontalmente attraverso l’utilizzo contemporaneo di più nodi MongoDB con l’Auto-Sharding, che possono funzionare sia per far scalare la dimensione dello storage, sia come repliche e quindi aumentare la tolleranza ai guasti e la velocità di I/O.
Tra gli utilizzatori di MongoDB citiamo il social network FourSquare, la Disney, bit.ly, SAP e SourceForge.
Per chi è abituato ad usare i classici database SQL (MySQL, Oracle, PostgreSQL) è un cambiamento radicale perchè MongoDB utilizza un linguaggio simile a Javascript e, quindi, la sintassi è completamente diversa rispetto al comune SQL. In qualsiasi caso, la sintassi non è tremendamente complessa, anzi, MongoDB punta a facilitare la fase di sviluppo rispetto ai comuni database relazionali.

In questo articolo proviamo a installare un server MongoDB e muovere i primi passi: queste informazioni sono liberamente tratte dal tutorial sul sito ufficiale.

Riguardo l’installazione, è parecchio semplice in quanto praticamente tutte le distribuzioni Linux hanno un pacchetto pronto da installare. Una volta installato il pacchetto, è sufficiente far partire il server (mongod) e attendere qualche istante per la prima inizializzazione.
MongoDB, al pari di altri database, ha una console per collegarsi al server e lanciare direttamente comandi: ovviamente gli stessi comandi possono essere lanciati anche dal nostro applicativo che si collegherà al database attraverso i driver disponibili per tutti i più diffusi linguaggi di programmazione.

Lanciamo quindi la console attraverso il comando “mongo”:

# mongo
MongoDB shell version: 2.0.1
connecting to: test
>

La console ci dice che ci siamo collegati al database “test”; per collegarsi ad un altro database esiste il comando “use”, simile all’analogo comando di MySQL.

> use mydb
switched to db mydb

Ecco una prima ma sostanziale differenza rispetto ai comuni database RDBMS: ci siamo collegati ad un database (mydb) senza averlo creato! Difatti, è proprio così: mydb non esisteva e, in realtà, non esiste ancora; mydb sarà creato dinamicamente nel momento in cui inseriremo dei dati. In MongoDB i database, gli schemi delle tabelle (anzi, in questo caso si chiamano “collection”) e gli indici sono creati dinamicamente all’occorrenza; quindi non dobbiamo preoccuparci di progettare al meglio il nostro database perchè anche lo schema (oltre al contenuto) crescerà insieme al nostro applicativo: una bella facilitazione!

Ora è arrivato il momento di inserire un po’ di dati: creiamo i due oggetti j e t e li salviamo all’interno della collection things.

> j = { name : "mongo" };
{"name" : "mongo"}
> t = { x : 3 };
{ "x" : 3  }
> db.things.save(j);
> db.things.save(t);
> db.things.find();
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
>

Usando la sintassi di JavaScript, è possibile usare i cicli direttamente nel motore database:

> for (var i = 1; i <= 20; i++) db.things.save({x : 4, j : i});
> db.things.find();
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
has more

La shell ha un limite di 20 linee, per vedere le successive è possibile usare il comando “it”

> it
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Da questo comando notiamo anche un’altra particolarità di MongoDB: il fatto di essere “schema free”. Difatti ogni dato memorizzato è un oggetto formato da contenuti di tipo e quantità diversa.

I dati estrapolati da una query sono immagazzinabili in un oggetto particolare chiamato “cursore”. Ripetiamo la precedente query memorizzando il dato in una variabile e usando un ciclo per stamparla a video:

> var cursor = db.things.find();
> while (cursor.hasNext()) printjson(cursor.next());
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Trattandosi di JavaScript, è possibile usare direttamente i metodi interni all’oggetto per ottenere il medesimo risultato:

> db.things.find().forEach(printjson);
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Proviamo ora a tradurre la query SQL “SELECT * FROM things WHERE x=4” nel JavaScript di MongoDB:

> db.things.find({x:4}).forEach(printjson);
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Il “SELECT *” non è mai buona cosa, per cui proviamo a tradurre la query “SELECT j FROM things WHERE x=4”

> db.things.find({x:4}, {j:true}).forEach(printjson);
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "j" : 20 }

Per una traduzione dei comandi SQL in sintassi MongoDB vi rimando a questa pagina del sito ufficale.

Ultima cosa che cito senza approfondire in questa sede: in caso di interrogazioni complesse con dati provenienti da diverse collection, è presente una potentissima funzione chiamata MapReduce che sostanzialmente crea una collection temporanea con il risultato dell’operazione.