MongoDB

De Cacauet Wiki
Salta a la navegació Salta a la cerca

Intro No-SQL

Algunes característiques:

  • Treballem amb documents.
  • La inconsistència de dades deixa de ser una prioritat.
  • Treballem amb incrustació (embedding). Exemple de blog: posts+comments
    • Dades relatives al document incrustades en el mateix (exemple de blog amb post+comment). Això ajuda a mantenir certa consistència.
    • Millora la velocitat d'accés (performance). Latència dels discs alta (1ms) però ample de banda alt (BW).
  • No té FKs. En canvi té operacions atòmiques.
  • No té transaccions (consistència temporal). Alternatives:
    • Reestructurar la nosta BBDD per treballar en un sol tipus de document (schema).
    • Implementar-la en el nostre software.
    • Tolerar certa inconsistència temporal (dependrà del tipus d'aplicació).
  • Relacions:
    • 1:1 millor sempre unir-les en un sol doc a no ser que excedeixi 16 MB (maxim).
    • 1:N si "N" son molts millor apuntar dels molts al 1 (amb el _id). També es pot fer al revés amb una llista.
      Hi ha una variant típica de 1:pocs que és el cas que ens agrada per incrustar.
    • N:N (molts:molts) calen llistes (es poden posar a les 2 col·leccions tot i que això significa que la app ha de controlar la consistència
  • Sempre intentem incrustar docs.

Info:

  • Schemaless and document based
  • No joins, no SQL
  • RDBS is not scalability in general machines
  • memcache is not highly functional but scales well
  • Joins don't scale well
  • Documents have a 16 MB limit on 32 bit OS's
  • Transactions are unavailable
  • Crud Operations exists as wire protocols
  • 'id' is immutable, and made up of Current Time, Machine Id, Process ID and global Counter
  • remove is not an isolated transaction.


Primeres passes

  • Monogo shell:
    $ mongo
  • Seleccionar una db (o crear):
    > use <nom_db>


JavaScript en la Mongo Shell

Això ens facilita molt la creació i manipulació de les dades. Per exemple, per crear un munt de documents que continguin un "num" i una "cosa" a triar aleatòriament entre "tablet", "smartphone" i "xocolata":

for (i=0;i<100000;i++) {
   db.coses.save( {
      "num":i,
      "cosa": ["tablet","smartphone","xocolata"][Math.floor(Math.random()*3)]
   })
}

Un altre exemple:

for (i=0;i<1000;i++) {
   names=["examen","treball","questionari"];
   for(j=0;j<3;j++) {
      db.notes.insert({
         "estudiant":i,
         "tipus": names[j],
         nota : Math.round(Math.random()*100)
      });
   }
}


CRUD

Create Read Update Delete


Create / Insert / Save

Per crear documents dins d'una col·lecció:

> db.<coleccio>.save( <JSON_obj> )


Read / Select / Find

La operació de query més estàndard en MongoDB és FIND. Alguns exemples:

> db.<coleccio>.findOne()
> db.<coleccio>.findOne( {"tipus":"examen"} )
> db.<coleccio>.find()
> db.<coleccio>.find( {estudiant:103} )

OJU: en Pymongo és find_one enlloc de findOne

Si ho volem veure una mica més ben formatat:

> db.<col>.find().pretty()

El 2n argument és per seleccionar camps (i excloure):

> db.<col>.find( {"tipus":"examen",nota:50}, {"estudiant":true,"_id":false} )

Operadors: estudiants amb examens amb notes > 90

> db.<col>.find( {"tipus":"examen", nota: {$gt:90} }, {"estudiant":true,"_id":false} )

Estudiants amb notes entre 65 i 71: db.grades.find( {"type":"exam",score: {$gte:65,$lte:71}} ).sort({"score":1})

Altres operadors: http://docs.mongodb.org/manual/reference/operator/query/

TODO: Unir dues queries amb $or...

Ordenar resultats (sort): http://docs.mongodb.org/manual/reference/method/cursor.sort/


Update

Referències:

La operació de Update es pot resoldre de 2 maneres:

  1. db.<col>.update( ... ) ...amb els següents operadors http://docs.mongodb.org/manual/reference/operator/update/#id1
    Exemple en mongo shell:
    self.posts.update( {"_id":33}, {$addToSet:{"comments":comment}} )
    Exemple en pymongo:
    self.posts.update( {"_id":33}, {"$addToSet":{"comments":comment}} )
  2. db.<col>.save( <doc> ) ...on <doc> ha de ser el nou document modificat amb el "_id" (PK) pertinent


Delete / Remove

Referències:


Indexes

Els indexes augmenten la velocitat de les consultes. Si no fos per ells, cada cop que fem una recerca (find) hauriem de repassar tota la BBDD, i si aquesta té una xifra elevada de registres (diguem-ne, 10 milions) la consulta serà lenta, i farà que el sistema sigui impracticable.

Referència: http://docs.mongodb.org/manual/indexes/

Vídeos de MongoDB University:

  • Introducció als índexes (segueix la sèrie de vídeos, hi ha diversos d'una durada de pocs minuts cadascun).
    • Un índex accelera les cerques
    • El mateniment de l'índex alenteix les insercions.
  • Índexes i performance (velocitat d'accés).
    • findOne() és més ràpid perquè quan troba una mostra ja surt.
    • si el ID que busquem és baix va ràpid (al principi de la col·lecció).
    • Si el ID que busquem és alt triga més.
    • find() triga igual perquè ha de buscar en tota la base de dades: lent (sense índex)
    • Buscar un element que no existeix: triga molta estona.
    • Selectivitat: millor triar índexes que no es repeteixin gaire (millor performance). Per ex. millor el quilometratge d'un cotxe que el color.
  • ...

Comandes

Per crear un índex (1 és ascendent i -1 descendent):

> db.<colleccio>.ensureIndex( {<camp1>:1,<camp2>:-1,...} , <opcions> }

Per destruïr-lo:

> db.<colleccio>.dropIndex({<camp1>:1,<camp2>:-1,...}}

Per saber els índexes de tot el sistema:

> db.system.indexes.find()

Per saber els índexes que té una col·lecció:

> db.<colleccio>.getIndexes()

Espai ocupat pels índex (ens importa per saber si es poden tenir a la RAM: performance):

> db.<colleccio>.getIndexSize()

Profiling (dades sobre el temps i indexes emprats per la consulta):

> db.<colleccio>.find(...).explain()

(temps = "millis")

Per forçar a utilitzar un índex determinat en una consulta a la Mongo Shell:

> db.<colleccio>.find(...).hint( {a:1} )

Oju, en pymongo es fa amb una llista, no amb un diccionari:

db.colleccio.find(...).hint( ['a', pymongo.ASCENDING] )

Si volem forçar a NO utilitzar cap índex:

> db.<colleccio>.find(...).hint( {$natural:1} )


Opcions

Camp únic (unique):

> db.<colleccio>.ensureIndex( {<camp>:1} , {unique:true} }

dropDups: Si la col·lecció té duplicats al crear índex amb camp únic ens els carreguem: (no molt recomanat)

> db.<colleccio>.ensureIndex( {<camp>:1} , {unique:true, dropDups:true} }

Sparse indexes: podem indexar camps que no els tenen tots els documents (en principi seríen "null") => aquests documents no s'indexaran

> db.<colleccio>.ensureIndex( {<camp>:1} , {unique:true, sparse:true} }

OJU, perquè els SPARSE indexes poden donar lloc a comportaments diferents en les consultes depenent si hem creat l'índex o no. Per exemple:

> db.<colleccio>.find().sort( {"size":1} )

...els documents sense el camp size apareixeran si NO hem creat el sparse index. Si el creem, no apareixeran.


Multi-indexes

Indexes sobre arrays, per ex, en documents com:

{
   title: "un post qualsevol",
   post: "cos del text",
   author: "enric",
   tags: ["btt","tennis","futbol"],
   categories: ["esports","hobbies"]
}

Es poden crear índexes sobre un array, però no sobre 2 alhora:

> ensureIndex( {tags:1} ) OK
> ensureIndex( {categories:-1} ) OK
> ensureIndex( {tags:1, categories:-1} )  NO ES POT

Posar un índex en un camp intern d'un document, per ex, en una col·lecció d'usuaris:

> ensureIndex( {adreces.telefons:1} )

(array d'adreces amb un camp telèfons incrustat)

OJU, amb aquests perquè poden arribar a ocupar molt espai, afectant a la performance de les insercions.


Aggregation: group queries

La llibreria aggregate ens permetrà fer consultes (queries) amb operacions com sum, avg, etc.

ULL: per la aggregate lib necessitem MongoDB en versió >= 2.4


Agrupació simple

Un exemple d'agrupació per manufacturer i que ens farà un recompte dels productes:

db.products.aggregate([
    {$group:
     {
	 _id:"$manufacturer", 
	 num_products:{$sum:1}
     }
    }
])

Una consulta similar en format "compacte":

> db.products.aggregate([{$group: { _id:"$category", num_products:{$sum:1} }} ])

Si només posem el camp que ens interessa (_id) obtindrem un llistat de les diferents "categories" (vindria a ser un DISTINCT de SQL:

> db.products.aggregate([{$group: {_id:"$category"}} ])

Agrupació composta

Per seguir diversos criteris d'agrupació només cal que els posem en un array (o diccionari) al _id del $group:

db.products.aggregate([
    {$group:
     {
	 _id: {
	 	"fabricant": "$manufacturer",
	 	"cat": "$category"
	 } ,
	 num_productes:{$sum:1}
     }
    }
])


Aggregation Pipeline (teoria)

El procés que realitza la llibreria el podem veure en aquests exemples:

Les diferents passes del pipeline son (no necessariament en aquest ordre i es poden fer diversos cops):

  • $project: select (1:1)
  • $match: filtra (n:1)
  • $group (n:1)
  • $sort (1:1)
  • $skip (n:1)
  • $limit (n:1)
  • $unwind: (1:n) genera una entrada per cada element d'array, si existeix un array incrustat en un document.

Fixeu-vos en què l'única que genera més entrades es la darrera: unwind (les altres o les deixa igual -sort- o bé les redueix).


Exercicis

...