MongoDB
Contingut
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
- Instal·lar darrera versió (2.4.8) a Ubuntu:
- Monogo shell:
$ mongo
- Seleccionar una db (o crear):
> use <nom_db>
- Exemple amb dades:
- Importar-lo:
$ mongoimport -d students -c grades < grades.ef42a2b3e7ff.js
- ...
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
- En Mongo Shell: http://docs.mongodb.org/manual/reference/crud/
- En PyMongo: http://api.mongodb.org/python/current/api/pymongo/collection.html
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:
- Mongo Shell Update: http://docs.mongodb.org/manual/tutorial/modify-documents/
- Pymongo Update: http://api.mongodb.org/python/current/api/pymongo/collection.html#pymongo.collection.Collection.update
- Update operators: http://docs.mongodb.org/manual/reference/operator/update/
La operació de Update es pot resoldre de 2 maneres:
- 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}} )
- Exemple en mongo shell:
- db.<col>.save( <doc> ) ...on <doc> ha de ser el nou document modificat amb el "_id" (PK) pertinent
Delete / Remove
Referències:
- Pymongo remove: http://api.mongodb.org/python/current/api/pymongo/collection.html#pymongo.collection.Collection.remove
- ...
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.
- http://docs.mongodb.org/manual/aggregation/
- http://docs.mongodb.org/manual/reference/operator/aggregation/
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 "pipeline" és el procés que realitza aggregation per obtenir els resultats:
- Vídeo: http://www.youtube.com/watch?v=WazN2DS8s8c&list=PLOqwUgCzNJcsC1ARX93IZZLf9k3rVRrCn
- Doc: http://docs.mongodb.org/manual/core/aggregation-pipeline/
- Exemples: http://docs.mongodb.org/manual/tutorial/aggregation-zip-code-data-set/
- Operadors: http://docs.mongodb.org/manual/reference/operator/aggregation/
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
...