Introduzione a MongoDB

MongoDB logo

MongoDB e' un moderno database documentale NoSQL che al tempo stesso offre le piu' avanzate funzionalita' di replica dati e di ricerca in parallelo (Map/Reduce).

MongoDB e' uno dei piu' diffusi DB NoSQL grazie alla sua velocita', alla flessibilita, alla semplicita' di installazione ed utilizzo.

Questo documento presenta gli aspetti principali di MongoDB. I primi capitoli valgono come introduzione: Installazione, Modello dati, Utilizzo. Quindi si passa agli aspetti un poco piu' particolari o complessi: Aggregazioni e Map/Reduce, Replica e Sharding, GridFS, Capped collection ed indici TTL, Transazioni (per ultimo ma e' il piu' importante). A questo punto e' possibile vedere gli aspetti piu' tecnici: Architettura, Amministrazione. Per finire un po' di Storia, ...
MongoDB e' un software Open Source distribuito con licenza GNU (Engine) ed Apache (per i driver). La versione di riferimento e' quella attuale di MongoDB [NdE 2Q14 versione 3.0] [NdA 4Q16 versione 3.2].

Installazione

Installare MongoDB e' molto facile su Linux (eg. RedHat, Fedora, CentOS, ...). Una volta impostato il repository /etc/yum.repos.d/mongodb-org-3.0.repo [NdA Aggiornamento 3.2: copiare dal sito ufficiale il file /etc/yum.repos.d/mongodb-org-3.2.repo ] basta lanciare il comando:

# yum install mongodb-org

In questo modo viene installato il demone (mongod) che gestisce il DB, la shell di accesso (mongo), il demone per il routing (mongos) ed i tool di gestione (eg. mongotop) della versione Community. In alternativa la versione Enterprise viene installata indicando il pacchetto mongodb-enterprise.

MongoDB e' disponibile per tutti i sistemi UNIX-based ed anche sulle piattaforme MS-Windows e Mac OS X. Su Mac OS X l'installazione consigliata e' con Homebrew:

$ brew update; brew install mongodb ... ==> Downloading https://homebrew.bintray.com/bottles/mongodb-3.2.4.el_capitan.bottle.tar.gz ... 🍺 /usr/local/Cellar/mongodb/3.2.4: 17 files, 208.7M

Gia' fatto! Ora si puo' utilizzare...

Modello dati

Anche se mi piace scrivere pagine molto pratiche, prima di utilizzare MongoDB e' necessaria un po' di teoria per capire cosa si sta cercando di fare... vediamo quindi il modello dei dati.

MongoDB registra i document su un database utilizzando il formato BSON (che e' una rappresentazione binaria del formato JSON):

db.agenda.save( {
    nome: "Bartolomeo", cognome: "Bogliolo", soprannome: "Meo", 
    eta: 60,
    esperienza: [ "MongoDB", "SQLite", "MySQL" ],
    telefono: "0123456789",	 
    peso : NumberLong(69)
  } )

Per inserire i dati non c'e' bisogno di fare nulla di piu': il primo inserimento crea direttamente la struttura necessaria (collection). Il campo chiave _id e' sempre presente e viene inserito automaticamente se non indicato. Ogni document ha un suo elenco di campi (field) assolutamente libero. Tuttavia tipicamente una collection ha document con un insieme di campi omogenei e contiene dati tra loro in relazione. Poiche' i nomi dei campi sono memorizzati per ogni documento, per collection di grandi dimensioni e' opportuno utilizzare nomi di colonna brevi.
I tipi di dati utilizzabili sono molto ampi: numeri (interi 32bit, interi 64bit, double), stringhe (UTF-8) ma anche array ed ObjectId.

L'equivalente relazionale di una collection e' una tabella, del document e' una riga, del field e' una colonna... ma tra i due modelli sono piu' le differenze che le analogie.

I dati possono essere estratti con l'operatore find che esegue la selezione dei dati con operatori e la proiezione (la colonna _id per default e' presente). find puo' essere modificato con l'ordinamento ed il limite:
 db.agenda.find( { eta: { $gt: 18 } }, { nome: 1, indirizzo: 1 } ).limit(5).sort(nome)

E' possibile definire indici secondari sulle collection indicando semplicemente l'elenco dei campi:
 db.agenda.createIndex( { cognome: 1 } )

I dati vengono estratti da una sola collection alla volta (non esiste il join). Per gestire le relazioni tra collection diverse nel disegno della base dati possono essere adottate due scelte: le references e gli embedded documents.
Con una reference viene posto il valore del campo _id della prima collection come campo nella seconda collection. In questo modo le applicazioni potranno utilizzare la find per trovare i dati collegati. Questo tipo di tecnica e' analoga al modello normalizzato per un DB relazionale (ma non c'e' l'SQL per fare i join e le ricerche corrette deve farle l'applicazione).
Con un embedded document un documento viene inserito all'interno di un altro documento. Un esempio classico e' quello di un cliente all'interno di una fattura:

db.fattura.save( {
    numero: 17,
    data: new Date(), 
    importo: 69.00,
    cliente: { RagSoc: "Acme ltd", vat: "12345678901" }
  } )

Poiche' MongoDB gestisce l'atomicita' delle transazioni solo a livello di documento, in questo modo e' possibile l'atomicita' di una operazione che comprende piu' documenti. Questa e' la tecnica piu' utilizzata, nonostante l'orrore di tutti i fanatici della normalizzazione.

Utilizzo

MongoDB puo' essere utilizzato da vari linguaggi. Sono disponibili driver per i piu' importanti linguaggi di programmazione (eg C, C++, C#, Java, Perl, PHP, Python, Node.js, Ruby, ...). MongoDB e' implementato dal processo mongod che e' tipicamente in ascolto sulla porta 27017 ed e' ovviamente richiamabile in modalita' client/server.
Inoltre e' disponibile una shell di accesso che utilizzeremo nei prossimi esempi: mongo. mongo ha un'interfaccia amichevole, accetta tutti i comandi MongoDB ed utilizza la sintassi ed i comandi JavaScript. Anziche' descrivere in dettaglio tutti i comandi... ecco finalmente un esempio pratico!

$ mongo MongoDB shell version: 3.0.4 connecting to: test Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ ... > help db.help() help on db methods db.mycoll.help() help on collection methods sh.help() sharding helpers rs.help() replica set helpers help admin administrative help help connect connecting to a db help help keys key shortcuts help misc misc things to know help mr mapreduce show dbs show database names show collections show collections in current database show users show users in current database show profile show most recent system.profile entries with time >= 1ms show logs show the accessible logger names show log [name] prints out the last segment of log in memory, ... use <db_name> set current database db.foo.find() list objects in collection foo db.foo.find( { a : 1 } ) list objects in foo where a == 1 it result of the last line evaluated; use to further iterate DBQuery.shellBatchSize = x set default number of items to display on shell exit quit the mongo shell

Insomma non c'e' bisogno di ricordare a memoria i comandi!

Proviamo ora a creare un database, ad inserire qualche dato ed a fare qualche operazione:

use mydb db.agenda.save( { nome: "Bartolomeo", cognome: "Bogliolo", soprannome: "Meo", eta: 60, esperienza: [ "MongoDB", "SQLite", "MySQL" ], telefono: "0123456789" } ) db.agenda.find( { eta: { $gt: 18 } }, { nome: 1, indirizzo: 1 } ).limit(5) { "_id" : ObjectId("6969690c893434e2a9f99bd"), "nome" : "Bartolomeo" } db.fattura.save( { numero: 17, data: new Date(), importo: 69.00, cliente: { RagSoc: "Acme ltd", vat: "12345678901" } } ) db.fattura.find({ "cliente.RagSoc": "Acme ltd" }) db.fattura.find({ "cliente.RagSoc": /^Acme/, numero: { $gt: 10 } }).sort({"cliente.RagSoc":1,importo:-1}) db.fattura.find({ $or: [{importo: { $lte: 10 }},{importo: { $gt: 1000 }}] }) db.agenda.find({sesso: { $ne: "M" } },{nome:1,telefono:1,_id:0}).limit(10) db.emp.insert({ _id: 7782, ename: "CLARK", deptno: 10, sal: 2450 }) db.emp.insert({ _id: 7369, ename: "SMITH", deptno: 20, sal: 800 }) db.emp.insert({ _id: 7934, ename: "MILLER", deptno: 10, sal: 1300 }) db.emp.update({},{$inc: {sal: 100}},{multi: true})

Le condizioni della find() possono essere complesse e riferirsi a tutti i campi, compresi quelli degli embedded document. Sono disponibili AND, OR, operatori matematici, espressioni regolari, ...
save() e' un wrapper per insert() ed update(): viene richiamato l'uno o l'altro a seconda che il campo _id sia presente o meno. La funzione update() modifica un solo record se non e' indicata la clausola multi. Per cancellare i documento si utilizza remove(). La funzione remove() cancella tutti i documenti corrispondenti alla condizione se non e' indicata la clausola justOne.

In MongoDB, come in praticamente in tutti i NoSQL, non esistono i join... quindi vanno realizzati applicativamente. Ecco un semplice esempio tra i tanti possibili perche' il disegno di un DB in mongo non e' necessariamente in 3NF (nell'esempio ci sono diversi accorgimenti: meditate gente, meditate):

db.emp.insert({ _id: 7499, ename: "ALLEN", deptno: 30, sal: 1600, comm: 300 }) db.dept.insert( { _id: 10, loc: "NEW YORK", dname: "ACCOUNTING" }) db.dept.insert( { _id: 20, loc: "DALLAS", dname: "RESEARCH" } ) db.dept.insert( { _id: 30, loc: "CHICAGO", dname: "SALES" } ) db.emp.find().forEach( function(i) { i.loc=db.dept.find( { _id: i.deptno }, { loc: 1, _id: 0 } ).toArray()[0]["loc"]; printjson(i); } ); db.empDept.drop() db.emp.find().forEach( function(i) { i.loc=db.dept.find( { _id: i.deptno }, { loc: 1, _id: 0 } ).toArray()[0]["loc"]; db.empDept.insert(i); } ); db.empDept.find()

Aggregazioni e Map/Reduce

A differenza dell'SQL in cui sono disponibili le clausole di GROUP BY ed HAVING, i database NoSQL tipicamente utilizzano l'algoritmo MapReduce. La caratteristica di questo algoritmo e' quella di essere molto adatto ad un'esecuzione parallela su server distinti spezzando le fasi delle singole elaborazioni da quella di raccolta finale dei dati.

Con MongoDB si hanno entrambe le possibilita' perche' dispone di costrutti specifici come aggregate() simili al GROUP BY dei database SQL:

db.zipcodes.aggregate( [ { $group: { _id: "$state", totalPop: { $sum: "$pop" } } }, { $match: { totalPop: { $gte: 10*1000*1000 } } } ] )

Ma naturalmente e' presente anche l'algoritmo Map/Reduce tipico dei database NoSQL:

var mapFunction1 = function() { emit(this.cust_id, this.price); }; var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; db.orders.mapReduce( mapFunction1, reduceFunction1, { out: "map_reduce_example" } )

Replica e Sharding

La distribuzione e la replica dei dati su piu' server e' una caratteristica fondamentale dei database NoSQL per supportare i BigData.
MongoDB dispone di due tecniche per la distribuzione dei dati: la replica e lo sharding.

Piu' database MongoDB possono essere sincronizzati tra loro in modo molto semplice. I database vengono aggiornati tra loro con una replicazione peer-to-peer incrementale implementata nativamente nell'engine.
La replica in MongoDB e' basata sui Replica Set (RS): un gruppo di nodi in cui il primario riceve le scritture e gli altri nodi le applicano. In caso di caduta di un nodo i replica set si allineano automaticamente e, se necessario, eleggono un nuovo primario. E' sempre opportuno utilizzare un numero dispari di nodi di Replica Set); se necessario e' possibile configurare un nodo Arbiter che non gestisce dati e quindi ha requisisti molto ridotti.
Per garantire la durabilita' di un Replica Set e' necessario che siano presenti almeno tre nodi con write concern impostato a w:majority (descritto nel prossimo capitolo).
Ecco i semplici comandi per configurare un Replica Set di 3 nodi (appena installati e senza autenticazione):

rs.initiate() rs.add("mongodb2.xenialab.it") rs.add("mongodb3.xenialab.it") rs.conf() rs.status()
La replica e' trasparente dal punto di vista della programmazione. Le applicazioni accedono via driver in scrittura verso il primary ma possono gestire le letture con modalita' differenti.

Per gestire basi dati di grandi dimensioni MongoDB permette di distribuire i dati tra nodi differenti applicando un partizionamento tra i nodi: lo sharding. La configurazione del cluster di sharding richiede diversi componenti: routers (mongos), config server, shard e replica set.
In una configurazione minimale e' presente un processo mongos cui si interfacciano le applicazioni e che si occupa del routing delle richieste ai nodi corretti. La configurazione degli shard e' invece mantenuta dal MongoDB config cui e' assegnato questo unico database. Infine i diversi shard su cui sono partizionati i dati sono database MongoDB singoli o RS.
Le collection poste in shard vengono divise in chunk a seconda del valore della chiave ed memorizzate su nodi diversi. Ad occuparsi della migrazione dei chunck e' il processo balancer che puo' essere attivato da ciascun nodo mongos.
Se l'architettura dello shard delle collection presenta qualche complessita' al contrario la loro gestione e' invece molto semplice. Prima viene dichiarato lo shard del database, quindi viene effettuato lo sharding di una collection partizionando i dati per una chiave (MongoDB supporta il range partitioning, l'hash partitioning e supporta il tag aware sharding):

db.runCommand( { enableSharding: agenda } ) sh.shardCollection( "mydb.agenda", { nome: "hashed" } )

Lo sharding introduce un elevato di scalabilita' orrizzontale senza introdurre complessita' dal punto di vista applicativo. Dal punto di vista applicativo non vi sono differenze nell'uso di collection in shard: l'unico requisito e' quello di collegarsi ai processi di routing mongos anziche' ai nodi mongod.
La chiave di sharding va scelta con attenzione perche' una scelta errata ha impatti prestazionali significativi e non puo' essere modificata senza ricostruire la collection.

Le tecniche di replica e di shard dei dati in MongoDB non sono alternative anzi si completano tra loro. Tipicamente anche per una piccola installazione di produzione si utilizza la replica per garantire la Durability dei dati.
Lo sharding e' invece rivolto alle installazioni piu' significative con decine o centinaia di server dedicati e tipicamente si appoggia sulla replica nel senso che uno shard e' tipicamente mantenuto da un replica set.

GridFS

La dimensione massima di un documento BSON e' 16M, se un documento e' di maggiori dimensioni va utilizzato il GridFS.
Un'importante funzionalita' di MongoDB e' quella di poter operare come File System distribuito. Come al solito vediamo un esempio (in Perl):

my $musicDir = "/Users/meo/Music/Best";
my $client = MongoDB::MongoClient->new(host => 'localhost', 
                                       port => 27017);
my $database = $client->get_database( 'dj' );
my $grid = $database->get_gridfs;
my @mFiles = getFiles($musicDir);

$grid->drop();
foreach my $file (@mFiles) {
    my $fh = IO::File->new("$musicDir/$file", "r");
    $grid->insert($fh, {"filename" => $file,
    "content-type" => "audio/mpeg", 
    "author" => "eMo"});
}

Capped collection ed indici TTL

Una collection cresce in modo indefinito e non sono presenti funzioni automatiche di riorganizzazione (eg. autovacuum). Oltre alle normali collection e' possibile utilizzare le Capped Collection che hanno una dimensione fissa e sono molto efficienti nel gestire dati temporanei. Una find() su una capped collection senza ordinamento recupera i dati in ordine naturale (LIFO). Quando la capped collection raggiunge la dimensione massima o il numero massimo di elementi, i nuovi inserimenti ricoprono i piu' vecchi. In una capped collection e' consentito l'update() ma il documento non puo' crescere di dimensioni, se si utilizza la replica anche la riduzione di dimensioni puo' generare problemi...
Un alternativa alle capped collection sono gli indici TTL (time to live) che consentono di definire quanto mantenere un dato. Un thread interno di mongod si occupa di cancellare periodicamente (per default ogni minuto) i dati che hanno superato il TTL.

db.createCollection("log", { capped : true, size : 1048576, max : 1000 } ) db.trace_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 7200 } )

Transazioni

Una premessa importante, se conoscete i concetti delle transazioni ACID sui database relazionali... dimenticate tutto: qui si riparte da zero!

L'atomicita' di una operazione e' garantita a livello di singolo documento. Il documento puo' contenere altri documenti in modalita' embedded e solo in questo modo si puo' garantire l'atomicita' della transazione su piu' documenti.

  {
    numero: 17,
    data: new Date(), 
    importo: 69.00,
    cliente: { RagSoc: "Acme ltd", vat: "12345678901" }
  }

L'atomicita' della gestione della fattura 17 e del cliente Acme e' cosi' garantita. Ma si tratta comunque di un solo documento definito nella stessa collection... Per modificarli assieme si e' denormalizzato il disegno del DB e se un cliente variasse (eg. la partita iva perche' errata) sarebbe necessario correggere ad una ad una tutte le fatture!

Le istruzioni che trattano piu' documenti (eg. update) vengono comunque eseguite per un documento alla volta e garantiscono l'atomicita' solo a livello del singolo documento.
Eventuali gestioni piu' complesse, requisiti differenti, ... possono solo essere gestiti applicativamente.

Il write concern descrive il livello di garanzia che MongoDB fornisce quando riporta il successo di un'operazione di scrittura. A seconda delle impostazioni, scelte nell'applicazione/configurazione, si possono avere diversi livelli di sicurezza sulla consistenza dei dati e, molto importante, differenti tempi di risposta. Per ottenere le migliori prestazioni basta... non controllare nulla!
Il write concern ha tre metriche impostabili:

La scelta sulla gestione delle transazioni e' quindi demandata alla progettazione che deve esplicitamente richiedere il write concern corretto alla tipologia di applicazioni utilizzate ed adottare un opportuno disegno della base dati.

Riassumendo le scelte di MongoDB sulle transazioni sono molto semplificative rispetto ai tradizionali DB relazionali. Non sono presenti le classiche proprieta' ACID (Atomicity, Consistency, Isolation e Durability) se non con limiti molto significativi. Queste scelte pero' consentono un'elevata velocita' nelle scritture, la distribuzione/replica dei dati e la continuita' operativa anche nel caso di partizionamento della rete. Insomma, come dice il famoso teorema CAP, bisogna sapersi accontentare! A proposito di CAP: MongoDB e' considerato un CP di default e puo' essere configurato come Eventually consistent.

Architettura

Dal punto di vista del sistema operativo MongoDB si presenta come un unico processo mongod in ascolto sulla porta TCP 27017. In realta' all'interno del processo operano diversi thread con compiti specifici. I thread di background sono una decina, inoltre ogni nuova connessione genera diversi thread. Ad ogni host che si collega vengono assegnate (per default) 10 connessioni gestite su un thread pool interno

I path utilizzati da MongoDB cambiano a seconda della piattaforma. Su Linux la configurazione si trova su /etc/mongod.conf, i file di database su /data/db, i log su /var/log/mongo.
Su MAC OS X i path sono analoghi ma, se installato con Homebrew, /usr/local/var/mongodb e' la directory principale con il dbPath ed il file mongod.lock, la configurazione e' su /usr/local/etc/mongod.conf mentre il log e' /usr/local/var/log/mongodb/mongo.log.
Su Windows il dbPath e' C:\DATA\DB.
Nella directory DBpath si trovano tutti i dati. Per ogni database e' presente un file .ns (Name Space) ed uno o piu' extent numerati progressivamente a partire da 0 (datafile). Il NameSpace contiene i puntatori ai dati effettivi mantenuti negli extent (che sono liste linkate contenute nei datafile e contengono uno o piu' document). Gli extent sono di grandi dimensioni poiche' MongoDB utilizza la tecnica della preallocazione con dimensione esponenzialmente crescente fino a 2G. Inoltre MongoDB non cancella fisicamente i documenti cancellati a meno che non venga esplictamente eseguita un'operazione di compact() o di repairDatabase().
Gli indici sono implementati come Btree ed ospitati sugli stessi datafile ma su extent loro dedicati.
db.stats() riporta lo stato corrente di tutte le allocazioni (eg. numero totale di documenti, spazio allocato sul file system, ...).

Lo storage engine MMAPv1 utilizza le tecnica dei memory-mapped files memorizzare i dati. Si tratta della stessa tecnica utilizzata dalla buffer file cache di Linux, e' quindi molto efficiente ed affidabile. Gia' con una base dati di medie dimensioni e' normale per MongoDB utilizzare tutta la memoria disponibile sul sistema!
Lo storage engine wiredTiger e' un nuovo engine introdotto dalla versione 3.0 che implementa i lock a livello di documento e consente la compressione dei dati. WT e' particolarmente efficiente nelle attivita' di scrittura poiche' non effettua il journaling.

La D dell'ACID viene garantita con il journaling delle operazioni. Se non richiesto dal write concern, il Journal viene aggiornato ogni 100 millisecondi (default modificabile con l'opzione --journalCommitInterval).
E' importante ricordare che le transazioni sono atomiche solo a livello di un singolo documento.

L'architettura di un Replica Set non e' complessa e prevede semplicemente un numero di nodi (preferibilmente dispari) su cui ospitare i dati.
Il nodo primario mantiene su una speciale capped collection (local.oplog.rs) le modifiche occorse ai dati. La dimensione dell'oplog e' definita al momento dell'avvio della prima replica; la dimensione e' proporzionale allo spazio libero disponibile e comunque dimensionata tra 1GB e 50GB. L'oplog non puo' essere variato di dimensioni in modo dinamico ma il resize richiede una procedura specifica. I nodi secondari utilizzano un heatbeat e mantengono una copia dell'oplog. In modo asincrono i membri secondari applicano a loro volta le stesse operazioni.

La configurazione del cluster di sharding richiede diversi componenti: router, config server e shard.
I router sono i processi mongos cui le applicazioni si collegano e che indirizzano gli accessi. I config server debbono essere 3 (in una configurazione di produzione) e sono normali database MongoDB che mantengono la configurazione del cluster. Gli Shard sono i database MongoDB che mantengono i dati e sono almeno due, ma tipicamente sono di piu' per disporre di una maggiore scalabilita' Ciascuno shard, per garantire la durability dei dati, e' a sua volta un replica set. In numero di nodi per ciascun replica set e' di almeno 3 ma, se si vogliono effettuare i backup in linea senza perdere la durability delle transazioni, sono necessari 5 nodi per ogni RS.
In una configurazione in shard tipicamente i processi mongos rispondono sulla porta 27017 (in questo modo e' trasparente l'accesso alle applicazioni che non cambiano nulla rispetto ad una configurazione con nodo singolo) ed i processi mongod utilizzano la porta 27018. Ma tutte le porte sono facimente modificabili/configurabili.
Le attivita' di balancing degli shard vengono effettuate da un thread specifico chiamato balancer che puo' essere attivato da un qualsiasi nodo mongos. Il balancer imposta un lock sul database config [NdE per controllarlo utilizzare il comando: db.locks.find( { _id : "balancer" } ).pretty() ], ed effettua lo spostamento dei chunk delle collection a seconda della configurazione presente. Per ovvie ragioni di consistenza quando il balancer e' attivo non possono essere eseguiti i backup. E' pero' possibile definire una finestra temporale in cui il balancer viene eseguito.

Il GridFS memorizza i file in due collection: fs.files che contiene i metadati e fs.chunks che contiene i dati binari.

Amministrazione

In una installazione tipica viene installato il demone (mongod) che gestisce il DB, la shell di accesso (mongo), il demone per lo sharding (mongos) ed i tool di gestione (mongoimport, bsondump, mongodump, mongoexport, mongofiles, mongooplog, mongoperf, mongorestore, mongostat, mongotop). Viene anche impostato il file di configurazione di default (eg. /etc/mongod.conf):

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  bindIp: 127.0.0.1

Con un'installazione Linux vengono automaticamente predisposti gli script di start/stop dei servizi.
Mentre su OS X i comandi sono:

# Manual startup: mongod --config /usr/local/etc/mongod.conf # Start mongodb at login ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents # Load mongodb now launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist

Per effettuare uno shutdown corretto di MongoDB ci sono diverse modalita'... ma non il kill -9:

use admin db.shutdownServer()

Mentre le applicazioni che utilizzano MongoDB vengono realizzate con linguaggi diversi ed accedono alla base dati mediante un apposito driver, il DBA tipicamente accede al sistema con la shell mongo che abbiamo gia' utilizzato fino ad ora! L'amministrazione di MongoDB, oltre alla conoscenza del DB, richiede conoscenze del sistema operativo ospite per diagnosticare velocemente gli eventuali problemi e del particolare disegno della base dati MongoDB per suggerire ai programmatori eventuali ottimizzazioni.

La gestione dello spazio e l'ottimizzazione dell'IO sono fondamentali per MongoDB. MongoDB, come la maggioranza dei DB NoSQL, non effettua automaticamente la compattazione degli spazi. Va utilizzato il comando db.repairDatabase() che e' lento, blocca l'accesso alla base dati e richiede la disponibilita' di circa il doppio dello spazio.
Quando si utilizzano le repliche un'operativa comune e' quella di sganciare una replica, effettuare la compattazione, risincronizzare la replica (che ora e' stata compattata) e quindi sganciare il primario e ripetere l'operazione su tutti i nodi.
Il comando db.collection.compact() ottimizza i dati all'interno di una collection ma non rilascia spazio al sistema operativo.

Sono possibili due tipologie di backup: fisico o logico.
Il backup fisico si effettua a database spento e deve comprendere tutti i datafile. In alternativa e' possibile utilizzare uno snapshot del File System, in questo caso il file system deve contenere sia il dbPache che i Journal.
Il backup logico si effettua con l'utility mongodump (ed il restore con l'utility mongorestore). L'utility mongoexport effettua un backup in formato JSON o CSV; tuttavia poiche' il formato JSON non rappresenta appieno tutte tipologia di dati BSON, benche' utile per la gestione di dati, non va utilizzato come strumento di backup.

Su MongoDB per default l'autenticazione non e' abilitata (parametro --auth). Ovviamente per un'installazione di produzione e' necessario abilitarla! Le utenze sono definite per database, le utenze del database admin possono agire su tutti i database. L'algoritmo per la generazione della password e' SHA256(MD5(utente:mongo:password)) senza salt.

Dal punto di vista delle ottimizzazioni e del tuning sono molti gli aspetti da considerare con MongoDB. Tuttavia le prestazioni dell'IO sono spesso il primo elemento da analizzare (eg. utilizzare XFS). Quando si utilizzano piu' sistemi in replica e' opportuno che i nodi abbiano caratteristiche analoghe.
MongoDB puo' essere installato su sistemi virtualizzati ed in cloud. Le versioni attuali sono state ottimizzate anche per supportare sistemi con alta latenza nell'accesso allo storage.
Anche i Replica Set hanno configurazioni specifiche per distribuire le elaborazioni su piu' datacenter (eg. priority 0 ed hidden per i siti di DR).

Infine vediamo alcuni script utili per l'amministrazione:

load("myJScript.js") host = db.serverStatus().host; prompt = function() { return db+"@"+host+"$ "; } db._adminCommand("listDatabases").databases.forEach(function (d) { mdb = db.getSiblingDB(d.name); printjson(mdb.stats()); }) db._adminCommand("listDatabases").databases.forEach(function (d) { mdb = db.getSiblingDB(d.name); mdb.getCollectionNames().forEach(function(c) { s = mdb[c].stats(); printjson(s); }) }) db.system.users.find().pretty()

Storia

Sviluppato inizialmente (2007) da 10gen come un componente PaaS e' stato trasformato in un progetto Open Source (2009) con un supporto e servizi di livello Enterprise.
Esistono diverse categorie di database NoSQL, i due piu' noti database NoSQL documentali sono MongoDB e CouchDB: entrambi sono velocissimi (a scapito delle proprieta' ACID), memorizzano i dati in BSON/JSON, sono scalabili in modo nativo su piu' nodi e non forniscono un'interfaccia SQL. MongoDB si e' rapidamente imposto sul mercato, diventando uno tra i piu' usati database non relazionali: e' infatti considerato il piu' diffuso database NoSQL, non solo come database documentale ma su tutte le catogorie.

Le versioni pari di MongoDB corrispondono a quelle di produzione mentre quelle dispari sono di sviluppo ed presentano nuove funzionalita'. L'attuale versione produzione [4Q16] e' la 3.2. La storia delle versioni e' riassunta in questo documento. Nonostante l'introduzione recente di MongoDB le sue versioni presentano variazioni architetturali significative (eg. algoritmo di write, replica set) ed e' quindi fortemente consigliato utilizzare le ultime versioni rilasciate.

Per una documentazione piu' dettagliata si rimanda al sito ufficiale di MongoDB ed in particolare al manuale.


Titolo: Introduzione a MongoDB
Livello: Medio (2/5)
Data: 14 Luglio 2014
Versione: 1.0.2 - 14 Febbraio 2016
Autore: mail [AT] meo.bogliolo.name