Curs MongoDB 2014 al PUE
Contingut
Temari
0000020600 Introducció a l'administració de BBDD NoSQL-MongoDB 2014-2015 Curs. Form. Prof. Cicles Formatius de Grau Mitjà i Superior 7/10/2014 - 11/11/2014 (30 hores) de 9:00h a 14:00h U Projecte Universitat Empresa Distrito 22@ - Av. Diagonal, 98-100 (Barcelona) Requisits de certificació Assistència a, com a mínim, el 80% de les sessions presencials Descripció L'objectiu d'aquest curs és explorar MongoDB, el motor de bases de dades NoSQL orientat a documents més popular. Segons el rànquing de BBDD de DB-Engines, MongoDB ocupa el 5è lloc en popularitat entre els motors d'emmagatzematge estructurat, sent amb ampli marge la solució NoSQL més popular. Per tant, aquest curs desgranarà què hi ha darrere d'aquest àmpliament acceptat motor NoSQL: les seves principals característiques, avantatges i desavantatges enfront de motors de bases de dades relacionals. IMPORTANT: No es necessària experiència prèvia amb MongoDB per a seguir el curs amb garanties, però sí que es recomana experiència en l'administració i ús d’algun motor de bases de dades relacionals. Javascript és el llenguatge amb el que s’interactua amb la Shell de Mongo, per la qual cosa seria interessant tenir com a mínim coneixements bàsics d’aquest llenguatge. Objectius El curs començarà tractant els conceptes bàsics de la instal·lació de MongoDB, per a continuar després veient l’estructura interna de fitxers de Mongo i les extenses capacitats de la seva shell usant Javascript, així com treballar amb documents JSON. Veurem com fer consultes, insercions i esborrats sobre documents, com importar i exportar dades, com dissenyar esquemes o models, com fer indexació, com treballar amb drivers per connectar les nostres aplicacions contra Mongo, ... Finalment, cobrirem les còpies de seguretat, com gestionar entorns de replicació i sharding, monitorització i recuperació de dades. Continguts Mòdul 1.- Introducció a mongoDB. 1.- Què és mongoDB? 2.- Primers passos. 2.1.- Conceptes bàsics: Documents, col·leccions i bases de dades. 2.2.- Instal·lació de mongoDB. 2.3.- Primers passos amb mongoDB. 2.4.- Introducció a la mongoDB Shell. 2.5.- Tipus de dades: JSON/BSON. 2.6.- Documentació de mongoDB. 3.- Creació, actualització i esborrat de documents. 3.1.- Insertar i guardar documents. 3.2.- Esborrar documents. 3.3.- Actualitzar documents. 3.4.- Establir una política de Write Concern. 4.- Consultes en mongoDB. 4.1.- Introducció a find i findOne. 4.2.- Criteris de consulta. 4.3.- Type-Specific Queries. 4.4.- $where Queries. 4.5.- Cursors. 4.6.- Comandes de bases de dades. Mòdul 2.- Dissenyant la teva aplicació. 1.- Índexs en mongoDB. 1.1.- Introducció als índexs en mongoDB. 1.2.- Ús de les comandes explain() i hint(). 1.3.- Quan no indexar? 1.4.- Tipus d'índexs. 1.5.- Administració d'índexs. 2.- Índexs especials i tipus de col·leccions. 2.1.- Capped Collections. 2.2.- Time-To-Live Indexes. 2.3.- Full-Text Indexes. 2.4.- Geospatial Indexing. 2.5.- Emmagatzemant arxius amb GridFS. 3.- Agregació en mongoDB. 3.1.- The Aggregation Framework. 3.2.- Pipeline Operations. 3.3.- MapReduce. 3.4.- Comandes d’agregació. 4.- Disseny de Schemas. 4.1.- Normalització versus Desnormalització. 4.2.- Optimitzacions per a la manipulació de dades. 4.3.- Gestió de la consistència. 4.4.- Migració de Schemas. 5.- mongoDB Drivers. 5.1.- Què son els drivers i com funcionen?. 5.2.- Exemples d'ús dels principals drivers. Mòdul 3.- Replicació en mongoDB. 1.- Configuració d'un Replica Set. 1.1.- Introducció a la replicació. 1.2.- Muntatge de prova en un minut. 1.3.- Configuració d'un Replica Set. 1.4.- Modificant la configuració d'un Replica Set. 1.5.- Com dissenyar un Replica Set. 1.6.- Opcions de configuració dels components d'un Replica Set. 2.- Components d'un Replica Set. 2.1.- Sincronització. 2.2.- Heartbeats. 2.3.- Eleccions. 2.4.- Rollbacks. 3.- Administració de Replica Sets. 3.1.- Inicialitzant els membres en Standalone Mode. 3.2.- Manipulació dels estats dels membres. 3.3.- Monitorització de la replicació. 3.4.- Master-Slave. Mòdul 4.- Sharding en mongoDB. 1.- Introducció a Sharding. 1.1.- Introducció a Sharding. 1.2.- Descripció dels components d'un clúster. 1.3.- Muntatge de prova en un minut. 2.- Configuració de Sharding. 2.1.- Quan aplicar Shard? 2.2.- Inici dels servidors. 2.3.- Com mongoDB rastreja les dades en un cluster. 2.4.- The Balancer. 3.- Escollir una Shard Key. 3.1.- Estratègies de Shard. 3.2.- Regles i directrius per escollir una Shard Key. 3.3.- Gestionant la distribució de les dades. 4.- Administració de Sharding. 4.1.- Visualització de l'estat actual. 4.2.- Gestió de connexions de xarxa. 4.3.- Administració del servidor. 4.4.- Equilibri de dades.
Sessió 1. 7-oct-2014
Jordi
LLibre que està bé per seguir:
- The Little MongoDB Book
refcardz: xuletaris de referència
- mongodbspain.com
Cada dia anirem seguint la carpeta slides. MongoDB és una base de dades NoSQL (not only SQL), orientada a documents. Base de dades documental. Però no és una base de dades orientada a arxius. És a dir, els documents no són arxius, sinó que són objectes.
Mongo ve de 'humongous', que significa 'enorme'.
- www.mongodb.org -> pàgina oficial del producte open source
- www.mongodb.com -> l'empresa, la part comercial.
MongoDB University -> https://university.mongodb.com/. Són cursos online de MongoDB amb pràctiques, amb el model de la plataforma edX.
Per ex, MongoDB for DBAs:
La database està formada per col.leccions (podem pensar-ho com a taules). I les col.leccions estan formades per Documents (podem pensar-ho com a files). Aquests Documents al final seran objectes. Aquests objectes es representen en JSON (Javascript Object Notation).
En JSON, els objectes estan entre les claus {}. Tot el que està entre claus és un objecte. Separat per comes tenim els atributs d'un objecte. Els atributs són string (entre dobles cometes), number, boolean, arrays, objectes (les dates seran objectes), i null.
{ "name":"Joan", "cognoms":"Quintana Compte", "email": ["joanqc@gmail.com", "jquintana@jaumebalmes.net"], "telefon": "636 51 77 85" }
No cal que tots els documents de la mateixa col.lecció tinguin la mateixa estructura. Hi ha documents amb més o menys atributs (i així no cal ficar atributs amb el valor null).
Aquests objectes es representen amb JSON, però es guarden en disc en format binari que es diu BSON. Aquest format és molt fàcil de recórrer programàticamen.
L'especificació oficial està en http://bsonspec.org.
La clau primària és obligatòria (_id), però MongoDB s'encarrega de gestionar-ho. El _id el pots posar tu o el posarà ell automàticament.
Auto-sharding: escalat horitzontal de dades. Les dades s'escalen i es repliquen entre diferents màquines, de forma fàcil.
Per provar la consola de mongo online:
Get started with `help` To try out an interactive tutorial type `tutorial`
> db test > show collections >
no hi ha col.leccions.
> db.students.insert({"name":"Joan", age:44})
Creo una col.lecció students (si no existeix, es crea). Sobre la col.lecció students, existeix l'operació insert, per inserir documents. Hem de passar com a argument el document que volem inserir. I el document l'hem de passar en format JSON.
L'objecte db em permet accedir a la bd actual.
> show collections students > show tables students
això és un recordatori de què les col.leccions serien com les taules, és una picada d'ull.
Per recuperar la informació que hem inserit:
> db.students.find() { "_id" : ObjectId("5433a58e1cdcaf0e38bc9f79"), "age" : 44, "name" : "Joan"
ho hem passat sense paràmetres, però en podríem haver posat. Fixem-nos que s'ha creat un id.
L'estructura és dinàmica, ara creo un document amb una altra estructura.
> db.students.insert({"name":"Pere", "cognoms":"Quintana Viaplana", age:5}) > db.students.find() { "_id" : ObjectId("5433a58e1cdcaf0e38bc9f79"), "age" : 44, "name" : "Joan" } { "_id" : ObjectId("5433a75140694756f62be08a"), "age" : 5, "name" : "Pere", "cognoms" : "Quintana Viaplana" }
> db.student.count() 0 > db.students.count() 2 > show collections students student
Compte! Mirem què ha passat. La col.lecció student no existia (faltava la s), i automàticament, al demanar count(), s'ha creat la col.lecció. Ara tinc dues col.leccions.
En instal.lar mongo es crea la carpeta mongo/bin, i aquí dins estan els executables (bàsicament el servidor mongod i el client mongo)
http://www.mongodb.org/ -> downloads
Instal.lar el mongodb és senzillament copiar les carpetes (no cal l'instal.lable, descomprimir el zip en una carpeta és més senzill). Per tant, ho podem tenir en un pen i copiar-ho en una altra màquina. No hi ha cap dependència ni shell.
S'ha instal.lat a C:\Program Files\MongoDB 2.6 Standard\bin
Welcome to MongoDB!
COMPONENTS
- bin/mongod - The database process. -> el servidor, el motor
- bin/mongos - Sharding controller. -> eina de replicació
- bin/mongo - The database shell (uses interactive javascript).
El port per defecte és el 27017.
A windows, el primer que fem és crear una nova entrada del PATH amb el valor C:\Program Files\MongoDB 2.6 Standard\bin
S'ha d'obir la consola cmd de Windows amb permisos d'administració. El server té moltes opcions. Si no li passo cap opció, no trobarà el directori de dades (\data\db does not exist).
Hem de configurar correctament un Data Directory. Ell pressuposa que és C:\data\db.
El que farem servir és arrencar mongo amb l'opció --dbpath, per dir-li on és el nostre directori de dades. Si creem la carpeta \data\db dins de la nostra carpeta de la instal.lació de mongo, és més fàcil, doncs copiant tota la carpeta ja podem exportar mongo a una altra màquina.
C:\> mongod.exe --help C:\> mongod.exe --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\db"
(es creen carpetes i fitxers dins de db)
Es creen dues bases de dades per defecte de sistema: admin i local.
Ctrl-C matem el procés.
Per defecte els missatges de log surten per la sortida estàndar. Amb l'opció --logpath, els missatges de log es guarden en un arxiu. Això és útil quan tenim el mongod com un servei (on no es veu la consola).
La base de dades local està en els arxius local.0 i local.ns (fixem-nos que no existeixen els equivalents de la bd admin).
Creem la carpeta logs/ on es guardaran els logs.
C:\> mongod.exe --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\db" --logpath "C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log" --logappend
El contingu del fitxer mongodb.log és:
2014-10-07T12:07:56.117+0200 [initandlisten] MongoDB starting : pid=1268 port=27017 dbpath=C:\Program Files\MongoDB 2.6 Standard\data\db 64-bit host=PUEPUE-1AVCOLIM 2014-10-07T12:07:56.118+0200 [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2 2014-10-07T12:07:56.118+0200 [initandlisten] db version v2.6.4 2014-10-07T12:07:56.118+0200 [initandlisten] git version: 3a830be0eb92d772aa855ebb711ac91d658ee910 2014-10-07T12:07:56.118+0200 [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49 2014-10-07T12:07:56.118+0200 [initandlisten] allocator: system 2014-10-07T12:07:56.118+0200 [initandlisten] options: { storage: { dbPath: "C:\Program Files\MongoDB 2.6 Standard\data\db" }, systemLog: { destination: "file", logAppend: true, path: "C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log" } } ...
Fitxer de configuració:
Creem la carpeta config/. En la versió 2.6 s'utilitza la nova versió dels paràmetres de configuració (YAML). (el format anterior es manté per compatibilitat).
Fitxer de configuració mongodb.conf: (en el format antic, però que encara s'usa):
#Specifies a TCP port to listen for client connections. port = 27017 #Specify the data directory path dbpath = C:\Program Files\MongoDB 2.6 Standard\data\db #Modify the storage pattern of the data directory to store each database’s files in a distinct folder. directoryperdb = false #Specify the path to a file name for the log file. logpath = C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log #Add new entries to the end of the logfile or overwriting the content of the log. logappend = true #Increases the amount of internal reporting returned on standard output or in the log file generated by logpath. verbose = true vvvvv = true
C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.conf"
El format YAML és bastant punyetero, s'ha d'anar amb compte. Per exemple, s'ha d'utiltizar espais i no tabulador; i entre una clau i un valor, després dels : ha d'anar un sol espai.
fitxer mongodb.cfg (amb format YAML):
# Archivo de configuración en formato YAML # Tutorial: http://learnxinyminutes.com/docs/es-es/yaml-es/ net: port: 27017 storage: dbPath: C:\Program Files\MongoDB 2.6 Standard\data\db directoryPerDB: false systemLog: destination: file path: C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log logAppend: true verbosity: 4
C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg"
Cercant a Google es troben diferents YAML validators.
Arrenquem mongo com a servei de Windows.
C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg" --install --serviceName "MongoDB Server 2.6" --serviceDisplayName "MongoDB Server 2.6 Service" --serviceDescription "MongoDB Server 2.6 Service" mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg" --install --serviceName "MongoDB Server 2.6" --serviceDisplayName "MongoDB Server 2.6 Service" --serviceDescription "MongoDB Server 2.6 Service"
No arrenca el servei, senzillament el deixa instal.lat.
Aleshores anem a Equipo > botón dercho Administrar > Servicios. Mirem el servei de Mongo, i diem que s'arrenqui automàticament.
net start "MongoDB Server 2.6"
El servicio de MongoDB Server 2.6 se ha arrancado correctamente.
Si vaig a Servicios, veuré que està arrencat correctament. Per aturar-lo:
net stop "MongoDB Server 2.6"
Per eliminar el servei:
mongod.exe --remove --serviceName "MongoDB Server 2.6"
Finalment, torno a instal.lar manualment el servei, i el marco per fer un inici automàtic. El proper dia ja tindré arrencat el servidor de Mongo només arrencada la màquina.
Ara ens connectarem al servidor Mongo, amb el client mongo.exe.
mongo --help
Hem de dir a quin servidor, per quin port, i a quina base de dades ens volem connectar. Per defecte, ens connectem a test.
mongo > exit mongo --host localhost --port 27017 > La versió curta mongo localhost:27017 > I ens connectem a una altra màquina mongo --host 192.168.20.131 --port 27017
Ens connectem a la màquina local pel port per defecte.
Per especificar la base de dades a la que ens volem connectar, tres opcions:
mongo test mongo localhost/test mongo localhost:27017/test
The .mongorc.js file is executed after the mongo shell is started, so if you set a default here it will override a database specified on the command line.
Crearem l'arxiu .mongorc.js (ocult) dins la nostra carpeta d'usuari. És un script d'arrencada.
Per tant, en la shell utilitzarem bàsicament Javascript per interactuar amb Mongo. Dins el fitxer .mongorc.js:
function saluda() { print("Hola!"); }
La idea és que el fitxer .mongorc.js es llegeixi per defecte Una altra possibilitat_
mongo --shell "C:\Users\mongodb-m\.mongorc.js"
I una altra possibilitat:
> load("C:/Users/mongodb-m/.mongorc.js")
Per mirar quines bases de dades tinc:
> show dbs admin (empty) local 0.078GB test (empty) > show databases admin (empty) local 0.078GB test (empty)
admin i test estan buides, i mentre estiguin buides no hi haurà cap fitxer en el directori. A local hi ha informació perquè de fet és on es guarden els logs.
Per canviar la base de dades
> use mydb
I si no existeix, es crea. De fet es crea l'àlies. Fins que no hi guardi cap dada (col.lecció o document) no es crearà físicament la base de dades.
Per saber la base de dades on estic:
> db test
Ajuda en línia:
Finalment, torno a instal.lar manualment el servei, i el marco per fer un inici automàtic. El proper dia ja tindré arrencat el servidor de Mongo només arrencada la màquina. Ara ens connectarem al servidor Mongo, amb el client mongo.exe. <pre> mongo --help
Hem de dir a quin servidor, per quin port, i a quina base de dades ens volem connectar. Per defecte, ens connectem a test.
mongo > exit mongo --host localhost --port 27017 > La versió curta mongo localhost:27017 > I ens connectem a una altra màquina mongo --host 192.168.20.131 --port 27017
Ens connectem a la màquina local pel port per defecte.
Per especificar la base de dades a la que ens volem connectar, tres opcions:
mongo test mongo localhost/test mongo localhost:27017/test
The .mongorc.js file is executed after the mongo shell is started, so if you set a default here it will override a database specified on the command line.
Crearem l'arxiu .mongorc.js (ocult) dins la nostra carpeta d'usuari. És un script d'arrencada.
Per tant, en la shell utilitzarem bàsicament Javascript per interactuar amb Mongo. Dins el fitxer .mongorc.js:
function saluda() { print("Hola!"); }
La idea és que el fitxer .mongorc.js es llegeixi per defecte Una altra possibilitat_
mongo --shell "C:\Users\mongodb-m\.mongorc.js"
I una altra possibilitat:
> load("C:/Users/mongodb-m/.mongorc.js")
Per mirar quines bases de dades tinc:
> show dbs admin (empty) local 0.078GB test (empty) > show databases admin (empty) local 0.078GB test (empty)
admin i test estan buides, i mentre estiguin buides no hi haurà cap fitxer en el directori. A local hi ha informació perquè de fet és on es guarden els logs.
Per canviar la base de dades
> use mydb
I si no existeix, es crea. De fet es crea l'àlies. Fins que no hi guardi cap dada (col.lecció o document) no es crearà físicament la base de dades.
Per saber la base de dades on estic:
> db test
Ajuda en línia:
> db.help() DB methods: db.adminCommand(nameOrDocument) - switches to 'admin' db, and runs c db.auth(username, password) db.cloneDatabase(fromhost) db.commandHelp(name) returns the help for the command db.copyDatabase(fromdb, todb, fromhost) db.createCollection(name, { size : ..., capped : ..., max : ... } ) ...
Sessió 2. 14-oct-2014
- https://mongolab.com/ -> mongoDB as a service. És una possibilitat de tenir una bd mongodb online i de forma gratuïta. Es podria utilitzar en un crèdit de síntesi.
A part de la shell, es pot treballar amb alguna eina gràfica multiplataforma com ara Robomongo.
Nosaltres treballarem en paral.lel de les dues maneres.
c:\> mongo -> per defecte es connecta a la base de dades test La meva IP és 192.168.20.218. La del company és: 192.168.20.129
D'entrada, les úniques bd que existeixen són admin i local.
Des de Robomongo, podem fer open shell sobre una connexió, i d'aquesta manera podem veure les comandes que s'executaran per sota.
En la consola la paraula clau important és db, que apunta a la bd per defecte.
> db test
En el Robomongo, Ctrl-Enter per tal d'executar una comanda de la shell. Amb Ctrl-Z podem recuperar les comandes que he executat anteriorment.
> db.help()
utilitzem use per canviar entre bases de dades.
Per començar a crear dades i col.leccions utilitzarem mongoimport. Partirem de documents JSON que volem carregar i que representa les dades que volem restaurar en el nostre servidor de mongo (que s'han exportat prèviament amb mongoexport). També es podria partir de fitxers CSV o TSV (tabuladors).
mongoimport està pensat per càrrega puntual de col.leccions. No és una eina per fer còpies de seguretat.
C:\> mongoimport --help C:\> mongoimport --host localhost:27017 --db twitter --collection tweets --type json --file "D:\tweets.json" --drop --stopOnError connected to: localhost:27017 2014-10-14T10:12:49.553+0200 dropping: twitter.tweets 2014-10-14T10:12:52.001+0200 Progress: 17468271/92307954 18% 2014-10-14T10:12:52.016+0200 9700 3233/second 2014-10-14T10:12:55.002+0200 Progress: 42435022/92307954 45% 2014-10-14T10:12:55.002+0200 23600 3933/second 2014-10-14T10:12:58.024+0200 Progress: 60890060/92307954 65% 2014-10-14T10:12:58.024+0200 33900 3766/second 2014-10-14T10:13:01.010+0200 Progress: 84175208/92307954 91% 2014-10-14T10:13:01.010+0200 46900 3908/second 2014-10-14T10:13:01.865+0200 check 9 51428 2014-10-14T10:13:01.866+0200 imported 51428 objects
Els arxius a importar han d'estar codificats amb UTF-8 per tal de què no hi hagi problemes.
Com que la base de dades twitter no existeix, es crearà. Recordem que tot és case-sensitive. Si la col.lecció no existeix, es crea. Hem d'indicar el format d'arxiu que volem importar (JSON, CSV, TSV,...)
--drop: si la col.lecció ja existeix, s'elimina i es torna a crear. --upsert: si ja existeix la col.lecció es pot actualitzar la informació a mida que es van afegint les noves dades. S'ha d'especificar en base a què vols actualtizar.
El format JSON en principi serà un objecte per fila. Però també podria ser un array d'objectes: tenim un array i els elements de l'array són els objectes que volem inserir. En aquest cas utilitzaríem l'opció jsonArray
S'han importat més de 51000 objectes que es correspon a tweets.
mongoimport --host localhost:27017 --db geography --collection zips --type json --file "D:\zips.json" --drop --stopOnError mongoimport --host localhost:27017 --db geography --collection zips --type json --file "D:\zips.json" --drop --stopOnError
mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json" --drop --stopOnError
mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json" --drop --stopOnError connected to: localhost:27017 2014-10-14T10:22:59.970+0200 dropping: geography.countries exception:BSON representation of supplied JSON is too large: code FailedToParse: FailedToParse: Expecting '{': offset:0 of:[ 2014-10-14T10:22:59.997+0200 check 0 0 2014-10-14T10:22:59.999+0200 imported 0 objects encountered 1 error(s)
falta el paràmetre --jsonArray, perquè el format del fitxer JSON és amb Array:
mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json" --drop --stopOnError --jsonArray
mongoimport --host localhost:27017 --db university --collection books --type json --file "D:\books.json" --drop --stopOnError mongoimport --host localhost:27017 --db university --collection scores --type json --file "D:\scores.json" --drop --stopOnError
Encara ens falta la col.lecció students, que té format CSV.
mongoimport --host localhost:27017 --db university --collection students --type csv --file "D:\students.csv" --drop --stopOnError --headerline --ignoreBlanks --fields "firstname,lastname1,lastname2,dni,gender,email,phone,phone_aux,birth_year" connected to: localhost:27017 2014-10-14T10:45:42.718+0200 dropping: university.students 2014-10-14T10:45:43.072+0200 check 9 3244 2014-10-14T10:45:43.105+0200 imported 3243 objects
L'opció --headerline és per dir que la primera línia conté els noms dels atributs.
L'opció --ignoreBlanks és per dir que els atributs buits no s'assignin a l'objecte.
I finalment, amb l'opció --fields hem de donar la llista dels camps que volem carregar, i en quin ordre (camps separats per comes i sense espais en blanc).
Utilitzar el format CSV és desaconsellable, perquè està limitat. Amb CSV un objecte només pot tenir atributs primitius (números, strings, ...). No pot ser que un atribut sigui alhora un objecte amb d'altres atributs.
Si mirem la col.lecció books, veiem que author i tags són arrays (doncs un llibre pot tenir diversos autors, i un llibre diferents tags). Això no ho podem fer si importem des d'un document CSV.
> show dbs admin (empty) geography 0.078GB local 0.078GB mydb (empty) twitter 0.203GB university 0.078GB
db.listCommands()
La manera d'executar les comandes llistades:
db.runCommand(...)
i a runCommand se li ha de passar com a paràmetre un objecte en format json.
db.runCommand({"listCommands":1})
i obtenim la mateixa informació que abans obtenint la informació en format json.
Si vull saber com està implementada aquesta funció amb Javascript:
> db.listCommands -> sense parèntesi function () { var x = this.runCommand("listCommands"); for (var name in x.commands) { var c = x.commands[name]; var s = name + ": "; switch (c.lockType) { case -1: s += "read-lock"; break; case 0: s += "no-lock"; break; case 1: s += "write-lock"; break; default: s += c.lockType; } if (c.adminOnly) { s += " adminOnly "; } if (c.adminOnly) { s += " slaveOk "; } s += "\n "; s += c.help.replace(/\n/g, "\n "); s += "\n"; print(s); } }
Per llistar les bases de dades, hem de pensar que show dbs o show databases és un àlies que en realitat fa:
> use admin switched to db admin > db.runCommand({"listDatabases":1}) { "databases" : [ { "name" : "geography", "sizeOnDisk" : 83886080, "empty" : false }, { "name" : "local", "sizeOnDisk" : 83886080, "empty" : false }, { "name" : "twitter", "sizeOnDisk" : 218103808, "empty" : false }, { "name" : "university", "sizeOnDisk" : 83886080, "empty" : false }, { "name" : "admin", "sizeOnDisk" : 1, "empty" : true }, { "name" : "mydb", "sizeOnDisk" : 1, "empty" : true } ], "totalSize" : 469762048, "ok" : 1 }
Si vull executar una comanda com a administrador:
> db.adminCommand(...) > db.adminCommand({"listDatabases":1})
Anem a mirar les col.leccions d'una bd, de diferents maneres:
> use university > show collections -> alies1 > show tables -> alies2 > db.getCollectionNames() -> la forma nadiua [ "books", "scores", "students", "system.indexes" ]
> db.stats() { "db" : "university", "collections" : 5, "objects" : 3786, "avgObjSize" : 288.2282091917591, "dataSize" : 1091232, "storageSize" : 3678208, "numExtents" : 14, "indexes" : 3, "indexSize" : 147168, "fileSize" : 67108864, "nsSizeMB" : 16, "dataFileVersion" : { "major" : 4, "minor" : 5 }, "extentFreeList" : { "num" : 0, "totalSize" : 0 }, "ok" : 1 }
Anem a trastejar una mica. La base de dades dummy no exiteix. Creo la col.lecció records. Inserto un document.
> use dummy > db.records.insert({"_id":1, "name":"Joan", "ranking":Math.random()}) > show collections > show databases
Fixem-nos que tinc accés en qualsevol moment a la llibreria de Javascript.
Hi ha una altra forma de crear una col.lecció, de forma explícita.
db.createCollection("test")
La gràcia de createCollection és passar-li paràmetres.
Una capped collection és la que té un límit de creixement, quant a tamany o quant a número màxim de documents.
db.createCollection("logs", {"capped":true, "size":5248000, "max":5000})
no admet un tamany d'arxiu de més de 5248000 bytes (obligatori si faig capped=true) i més de 5000 documents (opcional)
for (var i=0; i<=7000; i++) { db.logs.insert({"_id": i, "ranking":Math.random()}); }
Si mirem el document, veiem que ha manat el número màxim de documents. Es mostren els valors del 2000 al 7000, que són 5000 documents.
Anem a renombrar una col.lecció:
db.logs.renameCollection("records", 1)
El 1 és per dir-li que si existeix, carrega-te-la.
També:
> use admin > db.runCommand({"renameCollection":"dummy.records", "to":"twitter.records", "dropTarget":true})
és molt més potent, perquè hem mogut la col.lecció a una altra base de dades. També:
> db.adminCommand({"renameCollection":"twitter.records", "to":"dummy.records", "dropTarget":true})
Per eliminar una col.lecció:
> use twitter > db.records.drop() -> de fet és un àlies > use twitter > db.runCommand ({"drop":"records"})
Finalment per eliminar una base de dades. Anem a eliminar dummy:
> db dummy > db.dropDatabase() { "dropped" : "dummy", "ok" : 1 }
evidentment s'han eliminat totes les col.leccions que contenia.
Eina copyTo:
> use twitter switched to db twitter > show collections system.indexes tweets > db.tweets.copyTo("tweets.bsckup") 51428
> use twitter > db.tweets.backup.drop()
Fer consultes amb find.
Sobre una col.lecció apliquem un criteri, i després un modificador sobre els resultats:
db.users.find({ age: { $gt:18} }).sort({age:1})
Les consultes només es fan sobre una col.lecció. No hi ha JOINs com en bases de dades relacionades. El que passa és que la col.lecció ja pot tenir una estructura complicada d'informació.
> use university > db.students.find() -> em mostra el format json brut > db.students.find().pretty() -> em mostra el format json ben estructurat També: > db.students.find({})
Estudiants que han nascut el 1980:
> db.students.find({"birth_year":1980}) ... > db.students.find({"birth_year":1980}).count() 94
També es podria fer:
> db.students.count({"birth_year":1980})
ProjecciÓ.
db.users.find({criteria}, {projection})
la projecció és el que volem veure, o el que no volem veure, però no barrejar-ho. Ara bé, el _id sempre el veiem.
> db.students.find({"birth_year":1980}, {"dni":true}) { "_id" : ObjectId("543ce2b60dd38f399584c571"), "dni" : "05270652H" } { "_id" : ObjectId("543ce2b60dd38f399584c57e"), "dni" : "09190721J" } ... > db.students.find({"birth_year":1980}, {"dni":true, "_id":false}) { "dni" : "05270652H" } { "dni" : "09190721J" } ...
El _id és l'únic que podem dir aquests sí..., però el _id no...
Sessió 3. 21-oct-2014
mongodb_qrc_booklet.pdf. xuleta de queries. Cercar per Queries and What They Match
> use twitter > show tables > db.tweets.count() 51428
Per saber què podem fer a nivell de col.lecció:
db.collection.help() db.collection.find.help()
En aquesta sessió ens centrarem en el find per fer consultes, i també el Aggregation Framework que és molt més potent.
Anem a fer un exercici. Tenim la col.lecció bios penjada a Mongolab (un servidor gratis de que podem utilitzar a classe si creem un compte). L'exportarem amb mongoexport a una base de dades local (amb format JSON o CSV), i després la farem servir.
$ mongoexport --help
Les dades que necessitarem per fer la connexió són:
- host: ds039550.mongolab.com
- port: 39550
- Username: demo
- Password: demo
- Db: university
- Collection: bios
Per defecte els servidors de mongo no estan autenticats. Però aquest sí, hem de ficar usuari/pwd.
$ mongoexport -h ds039550.mongolab.com --port 39550 -d university -c bios -u demo -p demo -o bios.json connected to: ds039550.mongolab.com:39550 exported 10 records
Hi ha altres opcions, com ara passar-lo una consulta per filtrar, limitar el número de resultats, ordenar la sortida.
L'exportació pot ser en format array, o bé un document per fila (opció per defecte).
--jsonArray output to a json array rather than one object per line
El fitxer resultant:
{ "_id" : 1, "name" : { "first" : "John", "last" : "Backus" }, "birthYear" : 1924, "deathYear" : 2007, "contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ], "awards" : [ { "award" : "W.W. McDowell Award", "year" : 1967, "by" : "IEEE Computer Society" }, { "award" : "National Medal of Science", "year" : 1975, "by" : "National Science Foundation" }, { "award" : "Turing Award", "year" : 1977, "by" : "ACM" }, { "award" : "Draper Prize", "year" : 1993, "by" : "National Academy of Engineering" } ] } { "_id" : 2, "name" : { "first" : "John", "last" : "McCarthy" }, "birthYear" : 1927, "deathYear" : 2011, "contribs" : [ "Lisp", "Artificial Intelligence", "ALGOL" ], "awards" : [ { "award" : "Turing Award", "year" : 1971, "by" : "ACM" }, { "award" : "Kyoto Prize", "year" : 1988, "by" : "Inamori Foundation" }, { "award" : "National Medal of Science", "year" : 1990, "by" : "National Science Foundation" } ] } { "_id" : 3, "name" : { "first" : "Grace", "last" : "Hopper" }, "title" : "Rear Admiral", "birthYear" : 1915, "deathYear" : 1992, "contribs" : [ "UNIVAC", "compiler", "FLOW-MATIC", "COBOL" ], "awards" : [ { "award" : "Computer Sciences Man of the Year", "year" : 1969, "by" : "Data Processing Management Association" }, { "award" : "Distinguished Fellow", "year" : 1973, "by" : " British Computer Society" }, { "award" : "W. W. McDowell Award", "year" : 1976, "by" : "IEEE Computer Society" }, { "award" : "National Medal of Technology", "year" : 1991, "by" : "United States" } ] } { "_id" : 4, "name" : { "first" : "Kristen", "last" : "Nygaard" }, "birthYear" : 1906, "deathYear" : 2002, "contribs" : [ "OOP", "Simula" ], "awards" : [ { "award" : "Rosing Prize", "year" : 1999, "by" : "Norwegian Data Association" }, { "award" : "Turing Award", "year" : 2001, "by" : "ACM" }, { "award" : "IEEE John von Neumann Medal", "year" : 2001, "by" : "IEEE" } ] } { "_id" : 5, "name" : { "first" : "Ole-Johan", "last" : "Dahl" }, "birthYear" : 1926, "deathYear" : 2002, "contribs" : [ "OOP", "Simula" ], "awards" : [ { "award" : "Rosing Prize", "year" : 1999, "by" : "Norwegian Data Association" }, { "award" : "Turing Award", "year" : 2001, "by" : "ACM" }, { "award" : "IEEE John von Neumann Medal", "year" : 2001, "by" : "IEEE" } ] } { "_id" : 6, "name" : { "first" : "Guido", "last" : "van Rossum" }, "birthYear" : 1931, "contribs" : [ "Python" ], "awards" : [ { "award" : "Award for the Advancement of Free Software", "year" : 2001, "by" : "Free Software Foundation" }, { "award" : "NLUUG Award", "year" : 2003, "by" : "NLUUG" } ] } { "_id" : 7, "name" : { "first" : "Dennis", "last" : "Ritchie" }, "birthYear" : 1956, "deathYear" : 2011, "contribs" : [ "UNIX", "C" ], "awards" : [ { "award" : "Turing Award", "year" : 1983, "by" : "ACM" }, { "award" : "National Medal of Technology", "year" : 1998, "by" : "United States" }, { "award" : "Japan Prize", "year" : 2011, "by" : "The Japan Prize Foundation" } ] } { "_id" : 8, "name" : { "first" : "Yukihiro", "aka" : "Matz", "last" : "Matsumoto" }, "birthYear" : 1941, "contribs" : [ "Ruby" ], "awards" : [ { "award" : "Award for the Advancement of Free Software", "year" : "2011", "by" : "Free Software Foundation" } ] } { "_id" : 9, "name" : { "first" : "James", "last" : "Gosling" }, "birthYear" : 1965, "contribs" : [ "Java" ], "awards" : [ { "award" : "The Economist Innovation Award", "year" : 2002, "by" : "The Economist" }, { "award" : "Officer of the Order of Canada", "year" : 2007, "by" : "Canada" } ] } { "_id" : 10, "name" : { "first" : "Martin", "last" : "Odersky" }, "contribs" : [ "Scala" ] }
Ara he d'importar aquestes dades:
$ mongoimport --host localhost:27017 --db university --collection bios --type json --file "C:\Users\mongodb-m\bios.json" --drop --stopOnError connected to: localhost:27017 2014-10-21T09:42:19.228+0200 dropping: universit 2014-10-21T09:42:19.245+0200 imported 10 objects
> use university switched to db university > show collections bios books scores students system.indexes > db.bios.count()
Documentació oficial de MongoDB:
- docs.mongodb.org/manual/
find() admet dos paràmetres: (tot en format JSON)
- la consulta a realitzar
- la projecció (el filtratge)
Després es pot jugar amb els modificadors del cursor, per ex, per ordenar.
Millor posar pretty al final:
db.students.find() db.students.find().pretty()
db.students.find( { birth_year: { $gt:1970 } }, { firstname: 1, email: 1 } ).limit(5).pretty() "_id" : ObjectId("543ce2b60dd38f399584c569"), "firstname" : "Joan Ignasi", "email" : "litke@talktalk.net" "_id" : ObjectId("543ce2b60dd38f399584c56a"), "firstname" : "Tania", "email" : "owarne000s@talk21.com" "_id" : ObjectId("543ce2b60dd38f399584c56b"), "firstname" : "Guillermo", "email" : "seo@webindustry.co.uk" "_id" : ObjectId("543ce2b60dd38f399584c56d"), "firstname" : "Isabel", "email" : "robert.douglas3@virgin.net" "_id" : ObjectId("543ce2b60dd38f399584c56e"), "firstname" : "Carlos", "email" : "dan@webindustry.co.uk"
Només vull veure el dni (projecció) de tots els estudiants: (també mostrarà el id).
db.students.find( { }, { "dni": 1 } ).pretty() > db.students.find( { }, { "dni": 1 } ) { "_id" : ObjectId("543ce2b60dd38f399584c569"), "dni" : "01101634A" } { "_id" : ObjectId("543ce2b60dd38f399584c56a"), "dni" : "01749917P" } { "_id" : ObjectId("543ce2b60dd38f399584c56b"), "dni" : "02656865C" } { "_id" : ObjectId("543ce2b60dd38f399584c56c"), "dni" : "02666572K" } { "_id" : ObjectId("543ce2b60dd38f399584c56d"), "dni" : "02879308F" } { "_id" : ObjectId("543ce2b60dd38f399584c56e"), "dni" : "03448742F" } { "_id" : ObjectId("543ce2b60dd38f399584c56f"), "dni" : "05087108Z" } { "_id" : ObjectId("543ce2b60dd38f399584c570"), "dni" : "05206087Z" } { "_id" : ObjectId("543ce2b60dd38f399584c571"), "dni" : "05270652H" } { "_id" : ObjectId("543ce2b60dd38f399584c572"), "dni" : "05328197V" } { "_id" : ObjectId("543ce2b60dd38f399584c573"), "dni" : "05393518H" } { "_id" : ObjectId("543ce2b60dd38f399584c574"), "dni" : "05412086W" } { "_id" : ObjectId("543ce2b60dd38f399584c575"), "dni" : "05628736S" } { "_id" : ObjectId("543ce2b60dd38f399584c576"), "dni" : "05961345K" } { "_id" : ObjectId("543ce2b60dd38f399584c577"), "dni" : "07048302K" } { "_id" : ObjectId("543ce2b60dd38f399584c578"), "dni" : "07244064F" } { "_id" : ObjectId("543ce2b60dd38f399584c579"), "dni" : "07252345P" } { "_id" : ObjectId("543ce2b60dd38f399584c57a"), "dni" : "07500482K" } { "_id" : ObjectId("543ce2b60dd38f399584c57b"), "dni" : "07517306D" } { "_id" : ObjectId("543ce2b60dd38f399584c57c"), "dni" : "08838930F" } Type "it" for more >
Per no mostrar el id: "_id":0
db.students.find( { }, { "_id":0, "dni": 1 } ).pretty() { "dni" : "01101634A" } { "dni" : "01749917P" } { "dni" : "02656865C" } { "dni" : "02666572K" } { "dni" : "02879308F" } { "dni" : "03448742F" } { "dni" : "05087108Z" } { "dni" : "05206087Z" } { "dni" : "05270652H" } { "dni" : "05328197V" } { "dni" : "05393518H" } { "dni" : "05412086W" } { "dni" : "05628736S" } { "dni" : "05961345K" } { "dni" : "07048302K" } { "dni" : "07244064F" } { "dni" : "07252345P" } { "dni" : "07500482K" } { "dni" : "07517306D" } { "dni" : "08838930F" } Type "it" for more >
Exercicis:
*Buscar los estudiantes de género masculino <pre> db.students.find( { "gender": "H" } ).pretty() //Buscar los estudiantes de género femenino db.students.find( { "gender": "F" } ).pretty() //Buscar los estudiantes nacidos en el año 1993 db.students.find( { "birth_year": 1993 }).pretty() // Buscar los libros cuyo precio esté expresado en $ db.books.find( { "price.currency": "USD" }).pretty() //Buscar los estudiantes de génermo masculino y nacidos en el año 1993 db.students.find( { "gender": "H" , "birth_year": 1993 }).pretty() db.students.find( { "gender": "H" , "birth_year": 1993 }).count() Una altra manera amb l'atribut $and: -> posem un array amb les condicions a complir db.students.find( { "$and": [{"gender": "H"}, {"birth_year": 1993}] }).pretty() //Buscar los estudiantes nacidos en la década de los 90 operadors a la pàg 141 de la transparència. $gt (greater than), $gte (greater than or equal), $lt, $lte db.students.find( {"birth_year": {$gte:1990}, "birth_year": {$lt:2000}} ).pretty() 3243 db.students.find( { "$and": [{"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty() 387 Veiem que dóna resultats diferents. La versió separada per comes no es pot utiilitzar quan el mateix atribut interve en vàries de les condicions. Per tant, la segona és la correcta. //Buscar los estudiantes de género masculino nacidos en la década de los 90 db.students.find( { "gender": "H", "birth_year": {$gte:1990}, "birth_year": {$lt:2000}} ).pretty() Fent count() 2895 db.students.find( { "$and": [ {"gender": "H"}, {"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty() 339 o bé el que seria equivalent: db.students.find( { "$and": [ {"gender": "H"}, {"birth_year": {$gte:1990, $lt:2000}}] }).pretty() 339 Veiem que dóna resultats diferents. La versió separada per comes no es pot utiilitzar quan el mateix atribut interve en vàries de les condicions. Per tant, la segona és la correcta. //Buscar los estudiantes de género femenino nacidos en la década de los 90 db.students.find( { "$and": [ {"gender": "M"}, {"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty() amb count(): 48 //Buscar los estudiantes nacidos en la década de los 80 db.students.find( { "$and": [{"birth_year": {$gte:1980}}, {"birth_year": {$lt:1990}}] }).pretty() 936 //Buscar los estudiantes de género masculino nacidos en la década de los 80 db.students.find( { "$and": [{"gender": "H"}, {"birth_year": {$gte:1980}}, {"birth_year": {$lt:1990}}] }).pretty() 851 //Buscar los estudiantes que no han nacido en el año 1985 db.students.find( { "birth_year": {$ne:1990} }).pretty() 3145 //Buscar aquellos estudiantes que hayan nacido en el año 1970, 1980 o 1990 db.students.find( { "$or": [{"birth_year": 1970}, {"birth_year": 1980}, {"birth_year": 1990}] }).pretty() 293 //Buscar aquellos estudiantes que no hayan nacido en el año 1970, 1980 o 1990 db.students.find( { "$and": [{"birth_year": {$ne:1970}}, {"birth_year": {$ne:1980}}, {"birth_year": {$ne:1990}}] }).pretty() 2950 //Buscar los estudiantes nacidos en año par db.students.find({"birth_year": {$mod: [2,0]} }) 1684 //Buscar los estudiantes nacidos en año impar db.students.find({"birth_year": {$mod: [2,1]} }) 1559 //Buscar els estudiants en any parell de la dècada dels 70 de gènere masculí: db.students.find( {"$and": [{"birth_year": {$mod: [2,0]}} , {"birth_year": {$gte:1970}}, {"birth_year": {$lt:1980}}, {"gender": "H"} ] }) db.students.find( {"$and": [{"birth_year": {$mod: [2,0], $gte:1970, $lt:1980}}, {"gender": "H"} ] }) 403 //Buscar los estudiantes que tengan teléfono auxiliar (phone_aux) operador $exists db.students.find({"phone_aux": {$exists:1}}).pretty() 679 //Buscar los estudiantes que no tengan segundo apellido db.students.find({"lastname2": {$exists:0}}).pretty() 421 //Buscar los estudiantes que tengan teléfono auxiliar y solo un apellido db.students.find({"phone_aux": {$exists:1}, "lastname2": {$exists:0}}).pretty() 71 //Cercar tots els llibres que dins els tags conté html5. Hem de tenir present que tags és un array d'elements db.books.find({"tags": "html5"}).pretty() //Cercar tots els llibres que dins els tags conté html5, html, css o css3. podríem utilitzar $or, però utilitzarem $in (després de tags obrim un subdocument) db.books.find({"tags": {$in:["html5","html","css","css3"]}}).pretty() db.books.find( {$or:[{"tags":"html5"},{"tags":"html"},{"tags":"css"},{"tags":"css3"}]}).pretty() 14 //Cercar tots els llibres que dins els tags no continguin html5, html, css o css3. db.books.find({"tags": {$nin:["html5","html","css","css3"]}}).pretty() db.books.find( {$and:[{"tags":{$ne:"html5"}},{"tags":{$ne:"html"}},{"tags":{$ne:"css"}},{"tags":{$ne:"css3"}}]}).count() 319 //cercar els llibres que continguin els tags 'programming' i 'ageile' db.books.find({"tags": {$all:["programming","agile"]}}).pretty() db.books.find( {$and:[{"tags":"programming"},{"tags":"agile"}]}).pretty() 16 //cercar els estudiants que no han nascut el 1985 3147 //cercar els estudiants que han nascut el 70, 80, 90 db.students.find({"birth_year": {$in:[1970, 1980, 1990]}}).count() 293 //cercar els estudiants que no han nascut el 70, 80, 90 //cercar els llibres escrits per dos autors db.books.find({"author": {$size: 2}}).count() 79 //Buscar los estudiantes cuyo dni empiece por letra utilitzem expressions regulars (Javascript). Important db.students.find({"dni": {$regex: /^[A-Z]{1}/, $options: "i"}}).count() db.students.find({"dni": {$regex: /^[A-Z]{1}/i}}).count() db.students.find({"dni": /^[A-Z]{1}/i}).count() 248 L'opció i és que no importi majúscules i minúscules //Buscar los estudiantes cuyo dni empiece y termine por letra db.students.find({"dni": /^[A-Z]{1}.*[A-Z]{1}$/i}).count() 244 els que comencen i acaben amb lletra són els NULL, entre d'altres //Mirem els que tenen NULL com a dni: db.students.find({"dni": {$eq:"NULL"}}).count() 8 Anem a fer un update d'aquests registres. db.collection.update (query, update, options) Per defecte, el update només actualitza el primer element que troba. Si volem que actualitzi tots els documents, hem de passar l'opció multi. Per eliminar un camp s'ha de fer un unset. //eliminem el camp dels estudiants amb DNI NULL db.students.update ({"dni":"NULL"}, {$unset: {"dni": ""}}, {"multi": true} ) db.students.update ({"dni":"NULL"}, {$unset: {"dni": ""}}, {"multi": true} ) //cercar els estudiants que no tenen dni assignat: db.students.find({"dni": {$exists:false}}).count() són els 8 que abans eren NULL //Buscar los estudiantes cuyo teléfono o tel aux empiece por 622. db.students.find({$or: [{"phone": /^622/}, {"phone_aux": /^622/}]}).count() db.students.find({$or: [{"phone": /^6{1}2{1}2{1}/}, {"phone_aux": /^6{1}2{1}2{1}/}]}).count() db.students.find({$or: [{"phone": /^6{1}2{2}/}, {"phone_aux": /^6{1}2{2}/}]}).count() 201 //Buscar los estudiantes cuyo email termine en .net db.students.find({"email": /\.net$/i}).count() 47 //Buscar los estudiantes cuyo email termine en .org db.students.find({"email": /\.org$/i}).count() 16 //Buscar los estudiantes cuyo nombre empiece por vocal db.students.find({"firstname": /^[aeiouàáèéíòóúù]{1}/i}).count() 760 MongoDB no suporta collation //Buscar los estudiantes con nombre más largo de 13 caracteres db.students.find({"firstname": /^.{13,}$/}).count() 138 //Buscar los estudiantes con 3 o más 4en su nombre db.students.find({"firstname": /^.*[aeiouàáèéíòóúù]{1}.*[aeiouàáèéíòóúù]{1}.*[aeiouàáèéíòóúù]{1}.*$/i}).count() ######################################################################## //Buscar aquellos desarrolladores que sigan vivos //Buscar aquellos desarrolladores que hayan obtenido un premio en el año 2002 //Buscar aquellos desarrolladores que hayan obtenido algun premio entre el año 1995 y 2000 //Buscar aquellos desarrolladores que hayan obtenido exactamente 3 premios //Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP //Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP o Java //Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP y Simula //Mostrar todos los tweets publicados usuarios con más de 100 followers //Mostrar todos los tweets publicados por el usuario 'behcolin' //Mostrar aquellos paises cuya moneda es el euro //Mostrar aquellos paises donde el español es el idioma nativo //Mostrar aquellos paises donde el español es lengua oficial //Mostrar los tweets publicados por usuarios con el mismo numero de seguidores y amigos //Mostrar el número de tweets publicados por cada usuario
Sessió 4. 28-oct-2014
Recordatori per copiar de la shell de windows: selecciono, i Maj + botó mig del ratolí.
Avui veurem el Aggregation Framework, que és molt més potent, i és bastant recent. Era una demanda que hi havia, un framework de fer consultes més fàcil i potent del que fins ara es feia, que era mapReduce(), que estava basat en funcions de Javascript i era bastant complicat.
La idea és definir un Aggregation pipeline o workflow. Partim d'una col.lecció inicial, i la fem passar per diferents fases o estats, per obtenir finalment un resulat. A cada fase li apliquem un operador d'agregació.
Farem servir dues noves col.leccions, i per importar-les no farem servir export i import, sinó que farem servir mongodump i mongorestore. mongodump es pot fer servir per fer còpies de seguretat (totes les bases de dades, base de dades específica, una sola col.lecció,...). El resultat que obtenim és en format BSON (format binari).
base de dades imdb (Internet Movie Database). Descarregarem aquesta base de dades del servidor remot que tenim configurat. Hi ha 3 col.leccions.
mongodump --host ds039550.mongolab.com --port 39550 --username demo --password demo --db imdb --out backup mongodump --host ds039550.mongolab.com --port 39550 --username demo --password demo --db imdb --out backup
S'ha creat la carpeta backup/, a dins hi ha la carpeta imdb/, i a dints hi ha un arxiu per cada col.lecció, en format bson. També s'ha creat arxius per als index.
93 movies.metadata.json 76.937 oscars.bson 93 oscars.metadata.json 246.062 people.bson 93 people.metadata.json 198 system.indexes.bson
Per fer una còpia completa de la bd:
mongodump --host localhost --port 27017 --out dump
Per fer la restauració de tot el servidor o d'una base de dades, es fa de forma similar. He d'indicar la ruta on estan els arxius binaris
mongorestore --host localhost --port 27017 --db imdb --drop ".\backup\imdb" C:\Windows\system32>mongo MongoDB shell version: 2.6.4 connecting to: test > show databases admin (empty) geography 0.078GB imdb 0.078GB local 0.078GB twitter 0.453GB university 0.078GB >
I ara ja podem anar a la consola i assegurar-nos de què tenim la base de dades imdb
També restaurem la base de dades digg que ens proporcionen:
mongorestore --host localhost --port 27017 --db digg --drop "C:\Users\mongodb-m\digg" 2014-10-28T09:48:22.757+0100 C:\Users\mongodb-m\digg\stories.bson 2014-10-28T09:48:22.758+0100 going into namespace [digg.stories] 2014-10-28T09:48:22.759+0100 dropping 10000 objects found 2014-10-28T09:48:24.715+0100 Creating index: { key: { _id: 1 }, ns: "digg.sto ries", name: "_id_" } 2014-10-28T09:48:24.717+0100 Creating index: { key: { diggs: 1 }, ns: "digg.s tories", name: "diggs_1" } > show databases admin (empty) digg 0.078GB geography 0.078GB imdb 0.078GB local 0.078GB twitter 0.453GB university 0.078GB > use digg > db.stories.find().pretty()
Abans d'utilitzar el aggregate()', abans es feia servir les funcions de Javascript mapReduce, però això és complicat i està obsolet. (és Javascript pur).
Amb el mètode aggregate() li passem un array, i cada element de l'array és informació sobre el pas/estat que s'ha de processar. Cada estat és un subdocument:
aggregate( [ {}, {}, {} ] )
Utilitzem com operadors en cadascun dels passos/estats (els operadors comencen per $):
- $match
- $project
- $group
- $sort
- $limit
- $push -> aquest no tindria equivalent a SQL
- $addToSet -> aquest no tindria equivalent a SQL
- ...
En la carpeta Alumnos trobem la carpeta aggregation framework, que són els exercicis que anirem treballant. Recomanable utilitzar robomongo en comptes de la shell.
Primer de tot ens familiaritzem amb l'estructura de la col.lecció stories (de la base de dades digg).
db.stories.findOne() /* 0 */ { "_id" : ObjectId("4ba267dc238d3ba3ca000001"), "href" : "http://digg.com/people/Jedi_believer_who_refused_to_remove_hood_gets_an_apology", "title" : "'Jedi' believer who refused to remove hood gets an apology!", "comments" : 153, "container" : { "name" : "Offbeat", "short_name" : "offbeat" }, "submit_date" : 1268771801, "topic" : { "name" : "People", "short_name" : "people" }, "promote_date" : 1268915964, "id" : "19970068", "media" : "news", "diggs" : 404, "description" : "Chris Jarvis is a member of the International Church of Jediism - based on the famous Star Wars films - whose doctrine states followers can wear hoods.", "link" : "http://www.dailymail.co.uk/news/article-1258365/Jedi-believer-wins-apology-Jobcentre-kicked-wearing-hood.html", "user" : { "name" : "babychen", "registered" : 1141570067, "fullname" : "Babychen Mathew", "icon" : "http://digg.com/users/babychen/l.png", "profileviews" : 24749 }, "status" : "popular", "shorturl" : [ { "short_url" : "http://digg.com/d31Ln7s", "view_count" : 3682 } ] }
//Mostrar el número de recursos (podem dir-ne posts) publicados por cada usuario junto al número de comentarios y diggs obtenidos La idea és que per cada usuari hem d'obtenir tots els posts/recursos publicats, i cada recurs tindrà un número de comentaris i diggs que s'han de sumar. Seria l'equivalent al GROUP BY de SQL. Per tant, utilitzaré $group. db.stories.aggregate ([{}]) El primer que faré és una agrupació, $group: db.stories.aggregate ([{"$group": {_id: "$user.name"} }]) Utilitzarem un id per agrupar. De forma obligatòria hem de dir quin és l'atribut que utilitzarem com a id. I després passaré la informació que vull obtenir (pe ex, nom, cognoms,...). Utilitzaré com a _id el user.name. Ho ficaré amb $ ($user.name) doncs vull avaluar el valor d'aquest atribut. { "result" : [ { "_id" : "wabes2k" }, { "_id" : "diggdangdong" }, { "_id" : "trinityjr" }, { "_id" : "Landthatilove" }, { "_id" : "gmp24" }, ... Ara en aquesta agrupació vull posar més informació. Bàsicament sumar el número de posts. db.stories.aggregate ([{"$group": { _id: "$user.name", "posts": {$sum: 1} } }]) { "result" : [ { "_id" : "wabes2k", "posts" : 1 }, { "_id" : "diggdangdong", "posts" : 1 }, { "_id" : "trinityjr", "posts" : 2 }, { "_id" : "Landthatilove", "posts" : 2 }, ... Per tant, ara ja sé el número de posts que ha publicat cada usuari. I ara vull sumar el total de comentaris i diggs: db.stories.aggregate ([{"$group": { _id: "$user.name", "posts": {$sum: 1}, "comments": {$sum: "$comments"} , "diggs": {$sum: "$diggs"} } } ]) { "result" : [ { "_id" : "wabes2k", "posts" : 1, "comments" : 366, "diggs" : 498 }, { "_id" : "diggdangdong", "posts" : 1, "comments" : 400, "diggs" : 1039 }, ... Ara volem saber el valor màxim de puntuació en algun post, mínim valor de puntuació, i mitjana: db.stories.aggregate ([{"$group": { _id: "$user.name", "posts": {$sum: 1}, "comments": {$sum: "$comments"} , "diggs": {$sum: "$diggs"}, "max_diggs": {$max: "$diggs"}, "min_diggs": {$min: "$diggs"}, "avg_diggs": {$avg: "$diggs"} } } ]) ... { "_id" : "atomicpoet", "posts" : 5, "comments" : 1069, "diggs" : 4006, "max_diggs" : 2188, "min_diggs" : 320, "avg_diggs" : 801.2000000000001 }, ... Ara anem a aplicar un altre estat a l'agregat. Anem a ordenar-ho per número de posts amb l'operador $sort. db.stories.aggregate ([{"$group": { _id: "$user.name", "posts": {$sum: 1}, "comments": {$sum: "$comments"} , "diggs": {$sum: "$diggs"}, "max_diggs": {$max: "$diggs"}, "min_diggs": {$min: "$diggs"}, "avg_diggs": {$avg: "$diggs"} } }, {"$sort": { "posts": -1, "_id": 1} } ]) { "result" : [ { "_id" : "MrBabyMan", "posts" : 187, "comments" : 27538, "diggs" : 266837, "max_diggs" : 7958, "min_diggs" : 320, "avg_diggs" : 1426.935828877005 }, { "_id" : "LtGenPanda", "posts" : 169, "comments" : 21407, "diggs" : 208699, "max_diggs" : 6019, "min_diggs" : 335, "avg_diggs" : 1234.905325443787 }, ... Interessa sovint que l'últim pas del workflow és dir-li que tot el resultat me'l guardi en una col.lecció. Es fa utiitzant $out: db.stories.aggregate ([{"$group": { _id: "$user.name", "posts": {$sum: 1}, "comments": {$sum: "$comments"} , "diggs": {$sum: "$diggs"}, "max_diggs": {$max: "$diggs"}, "min_diggs": {$min: "$diggs"}, "avg_diggs": {$avg: "$diggs"} } }, {"$sort": { "posts": -1, "_id": 1} }, {"$out": "userstats" } ]) Resultat: { "result" : [], "ok" : 1 } I he obtingut la col.lecció userstats. És una mala pràctica en aquest cas utilitzar un guió baix, per ex user_stats.
//Mostrar para cada temática el número de recursos publicados junto a la lista de titulos La temàtica principal és container.name (després farem servir la temàtica secundària que és topic). Fixem-nos bé que si fem un agrupament, amb SQL no podríem obtenir la llista de títol. En canvi, aquí sí que ho podem fer gràcies a l'operador $push, que fica els valors dins d'un array. db.stories.aggregate ([ { "$group" : { "_id": "$container.name", "posts": {$sum: 1}, "titles": {$push: "$title"} } } ]) resultat: { "result" : [ { "_id" : "Sports", "posts" : 697, "titles" : [ "March Madness: Mastering the Art of Upsetology ", "March Madness: Mastering the Art of Upsetology ", "Former Jazz Center Arrested For Pot Between His Buttocks", "Anyone Know The Name Of This Wrestling Move? (GIF)", "10 Strange Athlete Superstitions", "5 People In Your NCAA Pool", "Olympic History: Who Has the Most Gold? (Graphic)", ... I ara ordenem: db.stories.aggregate ([ { "$group" : { "_id": "$container.name", "posts": {$sum: 1}, "titles": {$push: "$title"} } }, { "$sort": { "posts": -1, "_id": 1 } } ]) Resultats: { "result" : [ { "_id" : "World & Business", "posts" : 2002, "titles" : [ "Health Care Nightmares", "Loud sex enough for cops to search your home, court rules", "National Debt Up $2 Trillion on Obama's Watch ", "Son of Hamas - GQ.com", ...
//Mostrar para cada categoría el número de recursos publicados junto a la lista de titulos db.stories.aggregate ([ { "$group" : { "_id": "$topic.name", "posts": {$sum: 1}, "titles": {$push: "$title"} } }, { "$sort": { "posts": -1, "_id": 1 } } ])
//Mostrar per cada tema i categoria el número de recursos publicats. $project fa referència a projecció, el que vull veure a la sortida. db.stories.aggregate ([ {}, {}, {} ]) db.stories.aggregate ([ {"$group": {} }, {"$project": {} }, {"$sort": {} } ]) Fem servir un _id compost per fer l'agrupació. El _id està format per dos camps. db.stories.aggregate ([ {"$group": { "_id": {"theme": "$container.name", "topic": "$topic.name"}, "posts": {"$sum": 1}, "avg_diggs": {"$avg": "$diggs"}, "articles": {"$addToSet": { "title": "$title", "link": "$href" } } } }, {"$project": {} }, {"$sort": {} } ]) De moment provem: db.stories.aggregate ([ {"$group": { "_id": {"theme": "$container.name", "topic": "$topic.name"}, "posts": {"$sum": 1}, "avg_diggs": {"$avg": "$diggs"}, "articles": {"$addToSet": { "title": "$title", "link": "$href" } } } } ]) No volem el _id en la soritda (projecció): db.stories.aggregate ([ {"$group": { "_id": {"theme": "$container.name", "topic": "$topic.name"}, "posts": {"$sum": 1}, "avg_diggs": {"$avg": "$diggs"}, "articles": {"$addToSet": { "title": "$title", "link": "$href" } } } }, {"$project": { "_id": false, "theme": "$_id.theme", "topic": "$_id.topic", "lposts": "$posts", "larticles": "$articles" } }, {"$sort": {"posts": -1} } ]) Resultat: { "result" : [ { "theme" : "Sports", "topic" : "Golf", "lposts" : 14, "larticles" : [ { "title" : "Natalie Gulbis Must Think Lincoln Was REALLY Skinny (Pic)", "link" : "http://digg.com/golf/Natalie_Gulbis_Must_Think_Lincoln_Was_REALLY_Skinny_Pic" }, { "title" : "John Daly Tells the Golf Channel He Is 'Done' With Golf", Coses curioses que passen. He hagut de posar: "lposts": "$posts", "larticles": "$articles" en comptes de "posts": true, "articles": true per tal de què el theme i el topic surtin priner abans que els articles. db.stories.aggregate ([ {"$group": { "_id": {"theme": "$container.name", "topic": "$topic.name"}, "posts": {"$sum": 1}, "avg_diggs": {"$avg": "$diggs"}, "articles": {"$addToSet": { "title": "$title", "link": "$href" } } } }, {"$project": { "_id": false, "theme": "$_id.theme", "topic": "$_id.topic", "num_posts": {"$size": "$articles"}, "larticles": "$articles" } }, {"$sort": {"posts": -1} } ]) Resultat: { "result" : [ { "theme" : "Sports", "topic" : "Golf", "num_posts" : 10, "larticles" : [ { "title" : "Natalie Gulbis Must Think Lincoln Was REALLY Skinny (Pic)", "link" : "http://digg.com/golf/Natalie_Gulbis_Must_Think_Lincoln_Was_REALLY_Skinny_Pic" }, ...
Fem servir ara la base de dades imdb.
db.oscars.findOne() { "_id" : ObjectId("54497c1fe57946c4b14be7fb"), "year" : 2013, "type" : "BEST-PICTURE", "movie" : { "id" : "1024648", "name" : "Argo" } } db.movies.findOne() { "_id" : "0018379", "name" : "Seventh Heaven", "year" : 1927, "runtime" : 110, "genre" : "DR", "actors" : [ { "id" : "0310980", "name" : "Janet Gaynor" }, { "id" : "0268190", "name" : "Charles Farrell" }, { "id" : "0054135", "name" : "Ben Bard" }, { "id" : "0334581", "name" : "Albert Gran" }, { "id" : "0124877", "name" : "David Butler" } ], "directors" : [ { "id" : "0097648", "name" : "Frank Borzage" } ] }
//Buscar aquellas películas realizadas por Tom Cruise //Buscar aquellas películas dirigidas por Steven Spielberg //Buscar aquellas películas que no están clasificadas //Mostrar aquellas personas que han sido tanto actores como directores //Mostrar aquellas personas que ni han actuado ni han dirigido //Obtener el número de películas que hay para cada género ordenadas alfabéticamente //Obtener el número de películas que hay para cada clasificación ordenadas alfabéticamente
//Mostrar para cada actor el número de películas que ha realizado L'agrupament ha de ser per actor. Però els actors estan dins d'arrays associatius, doncs una pel.lícula té varis actors. Utilitzarem $unwind, que el que fa és que per cada document crea tants documents com elements hi ha en l'array. (unwind: desovillar, devanar) db.movies.aggregate ([ { "$unwind": "$actors" } ]) Amb això aconseguim tenir els actors a nivell de document. { "result" : [ { "_id" : "0018379", "name" : "Seventh Heaven", "year" : 1927, "runtime" : 110, "genre" : "DR", "actors" : { "id" : "0310980", "name" : "Janet Gaynor" }, "directors" : [ { "id" : "0097648", "name" : "Frank Borzage" } ] }, { "_id" : "0018379", "name" : "Seventh Heaven", "year" : 1927, "runtime" : 110, "genre" : "DR", "actors" : { "id" : "0268190", "name" : "Charles Farrell" }, "directors" : [ { "id" : "0097648", "name" : "Frank Borzage" } ] }, ... I ara ja puc continuar. db.movies.aggregate ([ { "$unwind": "$actors" }, { "$group": { "_id": "$actors.name", "movies": {$sum: 1} } }, {"$sort": {"movies": -1}} ]) resultat: { "result" : [ { "_id" : "Tom Cruise", "movies" : 13 }, { "_id" : "Tom Hanks", "movies" : 11 }, { "_id" : "Will Smith", "movies" : 10 },
//Mostrar para cada director el número de películas que ha dirigido Director també és un array, doncs en les pel.lícules també poden haver-hi varis directors. També hem de fer unwind. db.movies.aggregate ([ { "$unwind": "$directors" }, { "$group": { "_id": "$directors.name", "num_movies": {$sum: 1} } }, {"$sort": {"movies": -1}} ]) Resultat: { "result" : [ { "_id" : "Steven Spielberg", "num_movies" : 14 }, { "_id" : "William Wyler", "num_movies" : 9 }, ...
//Mostrar para cada director el número de películas realizadas, duración media, mayor y menor duración
db.movies.aggregate ([ { "$unwind": "$directors" }, { "$group": { "_id": "$directors.name", "num_movies": {$sum: 1}, "avg_runtime": {$avg: "$runtime"}, "max_runtime": {$max: "$runtime"}, "min_runtime": {$min: "$runtime"} } }, {"$sort": {"movies": -1}} ])
//Mostrar para cada actor el número de películas en las que ha participado, duración media, mayor y menor duración db.movies.aggregate ([ { "$unwind": "$actors" }, { "$group": { "_id": "$actors.name", "movies": {$sum: 1}, "avg_runtime": {$avg: "$runtime"}, "max_runtime": {$max: "$runtime"}, "min_runtime": {$min: "$runtime"} } }, {"$sort": {"movies": -1}} ]) Resultat: { "result" : [ { "_id" : "Tom Cruise", "movies" : 13, "avg_runtime" : 130, "max_runtime" : 154, "min_runtime" : 110 }, { "_id" : "Tom Hanks", "movies" : 11, "avg_runtime" : 130, "max_runtime" : 188, "min_runtime" : 81 },
//Para cada actor mostrar el listado de películas en las que han participado db.movies.aggregate ([ { "$unwind": "$actors" }, { "$group": { "_id": "$actors.name", "num_pelis": {$sum: 1}, "name": {$push: "$name"} } }, {"$sort": {"num_pelis": -1}} ]) Resultat: { "result" : [ { "_id" : "Tom Cruise", "num_pelis" : 13, "name" : [ "Color of Money, The", "Top Gun", "Rain Man", "Born on the Fourth of July", "Few Good Men, A", "Firm, The", "Jerry Maguire", "Mission: Impossible", "Mission: Impossible II", "Minority Report", "Mission: Impossible III", "War of the Worlds", "Mission: Impossible - Ghost Protocol" ] }, { "_id" : "Tom Hanks", "num_pelis" : 11, "name" : [ "Philadelphia", "Forrest Gump", "Apollo 13", "Toy Story", "Toy Story 2", "Green Mile, The", "Saving Private Ryan", "Catch Me If You Can", "The Polar Express", "The Da Vinci Code", "Toy Story 3" ] }, ... Ara bé, és millor fer primer un sort per tal de què per cada actor les pel.lícules surtin ordenades alfabèticament. db.movies.aggregate ([ {"$sort": {"name": 1}}, { "$unwind": "$actors" }, { "$group": { "_id": "$actors.name", "num_pelis": {$sum: 1}, "name": {$push: "$name"} } }, {"$sort": {"num_pelis": -1}} ]) { "result" : [ { "_id" : "Tom Cruise", "num_pelis" : 13, "name" : [ "Born on the Fourth of July", "Color of Money, The", "Few Good Men, A", "Firm, The", "Jerry Maguire", "Minority Report", "Mission: Impossible", "Mission: Impossible - Ghost Protocol", "Mission: Impossible II", "Mission: Impossible III", "Rain Man", "Top Gun", "War of the Worlds" ] },
//Para cada director mostrar el listado de películas que han dirigido db.movies.aggregate ([ { "$unwind": "$directors" }, { "$group": { "_id": "$directors.name", "num_pelis": {$sum: 1}, "name": {$push: "$name"} } }, {"$sort": {"num_pelis": -1}} ]) i si vull que les pel.lícules estiguin ordenades: db.movies.aggregate ([ {"$sort": {"name": 1}}, { "$unwind": "$directors" }, { "$group": { "_id": "$directors.name", "num_pelis": {$sum: 1}, "name": {$push: "$name"} } }, {"$sort": {"num_pelis": -1}} ]) Resultat: { "result" : [ { "_id" : "Steven Spielberg", "num_pelis" : 14, "name" : [ "Catch Me If You Can", "E.T. the Extra-Terrestrial", "Indiana Jones and the Kingdom of the Crystal Skull", "Indiana Jones and the Last Crusade", "Indiana Jones and the Temple of Doom", "Jaws", "Jurassic Park", "Lincoln", "Lost World: Jurassic Park, The", "Minority Report", "Raiders of the Lost Ark", "Saving Private Ryan", "Schindler's List", "War of the Worlds" ] }, //Mostrar las películas en las que ha participado Penelope Cruz Abans de fer l'agregat de tots els objectes, primer de tot faré un match per quedar-me amb les pelis en què ha participat Pe. db.movies.aggregate ([ {"$match": { "actors.name": "Penelope Cruz"} } ]) però millor fer-ho amb elemMatch: db.movies.aggregate ([ {"$match": { "actors": { "$elemMatch": {"name": "Penelope Cruz"} } } } ]) Continuem: db.movies.aggregate ([ {"$match": { "actors": { "$elemMatch": {"name": "Penelope Cruz"} } } }, { "$sort": {"title": 1} } ]) db.movies.aggregate ([ {"$match": { "actors": { "$elemMatch": {"name": "Penelope Cruz"} } } }, { "$sort": {"title": 1} }, { "$unwind": "$actors" } ]) hem fet un unwind d'actors, i estem tenint tots els actors que també han participat juntament amb la Pe. Per tant, hem de filtrar de nou: db.movies.aggregate ([ {"$match": { "actors": { "$elemMatch": {"name": "Penelope Cruz"} } } }, { "$sort": {"title": 1} }, { "$unwind": "$actors" }, { "$match": {"actors.name": "Penelope Cruz"} } ]) i ara puc fer un group (tot i que això no està en l'enunciat): db.movies.aggregate ([ {"$match": { "actors": { "$elemMatch": {"name": "Penelope Cruz"} } } }, { "$sort": {"title": 1} }, { "$unwind": "$actors" }, { "$match": {"actors.name": "Penelope Cruz"} }, { "$group": { "_id": "$actors.name", "num_pelis": {$sum: 1}, "name": {$push: "$name"} } } ]) Resultat: { "result" : [ { "_id" : "Penelope Cruz", "num_pelis" : 2, "name" : [ "Vicky Cristina Barcelona", "Pirates of the Caribbean: On Stranger Tides" ] } ], "ok" : 1 }
//Mostrar las películas que ha dirigido Peter Jackson //Obtener la película más reciente //Obtener la película más antigua //Obtener la película más larga de duración //Obtener la película más corta de duración //Mostrar los actores ordenados a partir de su año de nacimiento de forma descendente //Mostrar aquellos actores que han actuado junto a Penelope Cruz //Mostrar aquellos actores que han actuado junto a Tom Hanks //Mostrar las películas que han logrado oscars junto al número de oscars conseguido //Mostrar los actores que han logrado oscars junto al número de oscars conseguidos //Mostrar los actores que han logrado oscars junto a las películas y oscars conseguidos //Mostrar para cada año las películas y actores que han obtenido premios //Mostrar para cada año los distintos premios que se han entregado en los oscars
Sessió 5. 4-nov-2014
llibre: Seven databases in Seven Weeks
editorial: IT eBooks
Grid FS (Grid File Store). Podem utilitzar MongoDb per serialitzar arxius, guardar arxius a la base de dades. Cada objecte d'una col.lecció està limitat a 16MB, però això no és problema perquè els fitxers es divideixen en chunks de 255Kb. Grid FS treballa amb dues col.leccions:
- xx.files. Metadades de l'arxiu (títol, nom, data, autor,...)
- xx.chunks. És on es guarden els bytes, els arxius serialitzats, en chunks de 255 Kb.
Un bucket és un parell arxiu/metadades. Per ex, el bucket movies seria movies.files i movies.chunks.
Per interactuar amb Grid FS tenim dues opcions. Una opció és l'eina nativa mongofiles. L'altra opció és, des del nostre llenguatge de programació (JAVA, Python, .NET,...), utilitzar el driver/llibreria per a MongoDB.
Grid FS no és un sistema d'arxius complet. Bàsicament podem pujar un arxiu (put), get, delete, list, search.
$ mongofiles --help
En el servidor Mongolab (servidor remot del profe) hi ha la base de dades gridfs (fs.files, fs.chunks). El que farem és un get per descarregar arxiu que estan emmagatzemats. Dades de connexió:
Host: ds047720.mongolab.com Port: 47720 DB: gridfs Username: demo Password: demo
$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo list connected to: ds047720.mongolab.com:47720 Frogtek.mpg 47718404 Java-Specification-8.pdf 3938266 Agile Methods in Action.mp4 37215501 Scrum at Exact Research.mp4 14901085 Scrum Methodology an Agile Movie.mp4 37864324 $ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo list Scrum (que comencin per Scrum) $ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo search Scrum (que continguin Scrum)
$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo get "Scrum at Exact Research.mp4" connected to: ds047720.mongolab.com:47720 done write to: Scrum at Exact Research.mp4
Descarrega l'arxiu a la ruta on ens trobem, a no ser que utilitzi l'opció --local, que puc canviar-li la ruta i nom del nou arxiu que em descarrego.
Anem a pujar els arxius a la nostra bd. És important l'opció --type, el tipus MIME: (mirar a Google MIME types)
$ mongofiles --db gridfs --local "Java-Specification-8.pdf" --type "application/pdf" put "Java-Specification-8.pdf" connected to: 127.0.0.1 added file: { _id: ObjectId('545898b7597375c9d763e61e'), filename: "Java-Specifi cation-8.pdf", chunkSize: 261120, uploadDate: new Date(1415092407559), md5: "22c 706198c4e653d876be871b09b3a3c", length: 3938266, contentType: "video/mpeg" } done! $ mongofiles --db gridfs --local "Scrum at Exact Research.mp4" --type "video/mpeg" put "Scrum at Exact Research.mp4" <pre> Per llistar els arxius: <pre> $ mongofiles --db gridfs list Scrum at Exact Research.mp4 14901085 Java-Specification-8.pdf 3938266
Anem al nostre servidor Mongo:
C:\Users\mongodb-m>mongo MongoDB shell version: 2.6.4 connecting to: test > use gridfs switched to db gridfs > show collections fs.chunks fs.files system.indexes
col.lecció files: (> db.fs.files.find())
{ "_id" : ObjectId("54589971bed87f39cd19cb0b"), "filename" : "Scrum at Exact Research.mp4", "chunkSize" : 261120, "uploadDate" : ISODate("2014-11-04T09:16:33.916Z"), "md5" : "ba194b3a2fb1db7e1c0016df02b80914", "length" : 14901085, "contentType" : "video/mpeg" } /* 1 */ { "_id" : ObjectId("54589a18dc1d9c49c385a659"), "filename" : "Java-Specification-8.pdf", "chunkSize" : 261120, "uploadDate" : ISODate("2014-11-04T09:19:20.958Z"), "md5" : "22c706198c4e653d876be871b09b3a3c", "length" : 3938266, "contentType" : "application/pdf" }
Col.lecció chunks:
{ "_id" : ObjectId("5458997109fd9c478cd959e6"), "files_id" : ObjectId("54589971bed87f39cd19cb0b"), "n" : 0, "data" : { "$binary" : "AAAAHGZ0eXBtcDQyAAAAAU00ViBtcDQyaXNvb...} /* 1 */ { "_id" : ObjectId("5458997109fd9c478cd959e7"), "files_id" : ObjectId("54589971bed87f39cd19cb0b"), "n" : 1, "data" : { "$binary" : "CA6deEehDcLxrftGFtXI/8r0diMkQk4uD...} ...
I ara em descarrego l'arxiu des de la meva bd:
$ mongofiles --db gridfs --local "Scrum.mp4" get "Scrum at Exact Research.mp4"
Esborro un fitxer:
$ mongofiles --db gridfs --local "Scrum.mp4" delete "Scrum at Exact Research.mp4" done!
Mongo no treballa amb fusos horaris. Treballa amb format GMT. Per treballar amb dates el millor és treballar amb Aggregation Framework. Anem a fer proves amb dates:
Hi ha operadors d'agregat per treballar amb dates i extreure la informació de les dates. Per ex, cercar $year en la documentació.
db.fs.files.aggregate( [ { "$group": { "_id": { "month": {"$month": "$uploadDate"}, "year": {"$year": "$uploadDate"} } } } ] )
resultat:
{ "result" : [ { "_id" : { "month" : 11, "year" : 2014 } } ], "ok" : 1 }
db.fs.files.aggregate( [ { "$group": { "_id": { "month": {"$month": "$uploadDate"}, "year": {"$year": "$uploadDate"} }, "num_files": {"$sum": 1}, "total_length": {"$sum": "$length"}, "files": { "$push": { "filename": "$filename", "mime": "$contentType" } } } } ] )
resultat:
{ "result" : [ { "_id" : { "month" : 11, "year" : 2014 }, "num_files" : 2, "total_length" : 18839351, "files" : [ { "filename" : "Java-Specification-8.pdf", "mime" : "application/pdf" }, { "filename" : "Scrum at Exact Research.mp4", "mime" : "video/mpeg" } ] } ], "ok" : 1 }
Consultes de geolocalització. Està molt integrat a MongoDB. Primer de tot necessitarem una consulta ben dissenyada.
- Consultes de proximitat: $near. Per ex, cercar els 100 elements més propers a un punt.
- consultes basades en regions: cercle, box, poligon. $geoWithin
Pdem cercar a Google per world.geo.json. Per exemple, podem trobar el límit dels països en format geo.json.
Fixar-se que en Mongo sempre es demana primer la longitud i després la latitud:
loc: [<longitude>, <latitude>]
o bé:
loc: {lng: <longitude>, lat: <latitude>}
Descarreguem aquesta base de dades, que es troba per Internet, on hi ha més de 7000 ciutat geolocalitzades.
Host: ds047800.mongolab.com Port: 47800 DB: geography Username: demo Password: demo
La inserim dins de geography.
$ mongoexport --host ds047800.mongolab.com --port 47800 --db geography --username demo --password demo --collection cities --out cities.json o bé $ mongodump --host ds047800.mongolab.com --port 47800 --db geography --username demo --password demo --collection cities --out backup
Són 98838 registres. (recordem que --out backup vol dir que ho fica dins la carpeta backup/).
... { "_id" : { "$oid" : "54579597e9cd9f37d7fcacd8" }, "name" : "Canillo", "country" : "AD", "timezone" : "Europe/Andorra", "population" : 3292, "loc" : { "longitude" : 1.6, "latitude" : 42.56667 } } ...
Ara hem de fer el mongoimport:
C:\> mongoimport --db geography --collection cities --type json --file "C:\Users\mongodb-m\cities.json" --drop --stopOnError connected to: 127.0.0.1 2014-11-04T12:24:03.042+0100 dropping: geography.cities 2014-11-04T12:24:06.022+0100 Progress: 7385498/20433856 36% 2014-11-04T12:24:06.038+0100 36400 12133/second 2014-11-04T12:24:09.003+0100 Progress: 14880627/20433856 72% 2014-11-04T12:24:09.003+0100 73400 12233/second 2014-11-04T12:24:11.077+0100 check 9 99838 2014-11-04T12:24:11.093+0100 imported 99838 objects C:\Users\mongodb-m>mongo MongoDB shell version: 2.6.4 connecting to: test > use geography switched to db geography > show collections cities countries system.indexes zips
<pre> > db.cities.find().pretty() ... { "_id" : ObjectId("54579597e9cd9f37d7fcacdc"), "name" : "Ra's al Khaymah", "country" : "AE", "timezone" : "Asia/Dubai", "population" : 115949, "loc" : { "longitude" : 55.94278, "latitude" : 25.79111 } } ...
Ara ja podem començar a treballar.
> db.system.indexex.find()
Veiem que només hi ha index associats a _id, per les diferents col.leccions. Seria una bona idea crear un index per a longitud i latitud, per tal de facilitar les cerques.
Anem a fer un exemple d'index:
> use university db.students.findOne({"dni": "Y2912569R"}) { "_id" : ObjectId("54461df50713cb58f0ab4c0a"), "firstname" : "Andrés", "lastname1" : "Abarca", "dni" : "Y2912569R", "gender" : "H", "email" : "adamsith@07168.cn", "phone" : "669.559.922", "birth_year" : 1977 }
recorre els 3000 i picos objectes fins que finalment el troba db.students.find({"dni": "Y2912569R"}).explain()
> db.students.find({"dni": "Y2912569R"}).explain() { "cursor" : "BasicCursor", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 3243, "nscanned" : 3243, "nscannedObjectsAllPlans" : 3243, "nscannedAllPlans" : 3243, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 25, "nChunkSkips" : 0, "millis" : 3, "server" : "PUEPUE-1AVCOLIM:27017", "filterSet" : false }
Veiem que ha escanejat 3243 objectes, i ha trigat 3 ms. Anem a millorar aquesta cerca, creant un index: (createindex està deprecated)
db.students.ensureIndex({"dni": 1}, {"name": "dni_idx", "unique": true}) -> dóna error perquè hi ha camps repetits de DNI. Ho executem sense el unique db.students.ensureIndex({"dni": 1}, {"name": "dni_idx"}) db.students.getIndexes() [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "university.students" }, { "v" : 1, "key" : { "dni" : 1 }, "name" : "dni_idx", "ns" : "university.students" } ]
Es crea una estructura d'arbre per indexar els objectes (b-tree). Consumeix espai en els arxius. Cada vegada que es creï un registre es fa una reindexació.
Ara tornem a fer el explain, i veiem com la consulta triga 0 ms i que el número d'objectes escanejats només és 1:
> db.students.find({"dni": "Y2912569R"}).explain() { "cursor" : "BtreeCursor dni_idx", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 1, "nscanned" : 1, "nscannedObjectsAllPlans" : 1, "nscannedAllPlans" : 1, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 0, "indexBounds" : { "dni" : [ [ "Y2912569R", "Y2912569R" ] ] }, "server" : "PUEPUE-1AVCOLIM:27017", "filterSet" : false }
Millorem l'index, dient-li que només es creï l'index per aquells registres que tenen un valor per al dni:
db.students.dropIndex("dni_index") db.students.ensureIndex({"dni": 1}, {"name": "dni_idx", "unique": true,"sparse":true})
El problema dels index és que no es poden abusar d'ells, doncs si fas index per tot, es pot veure que el tamany dels arxius que ocupen els index pot ser molt més gran que el que ocupen els fitxers.
Tornant a l'exemple de geografia, hem de crear un index en el camp que conté les coordenades de geolocalització (longitud, latitud), o bé en format GeoJSON.
Per començar és més fàcil treballar amb u index 2d (després treballarem amb el index 2dsphere):
> use geography db.cities.ensureIndex({"loc": "2d"}, {"name": "geo_idx"}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } db.cities.getIndexes() [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "geography.cities" }, { "v" : 1, "key" : { "loc" : "2d" }, "name" : "geo_idx", "ns" : "geography.cities" } ]
Consultes de proximitat: 100 elements més propers a un punt.
near es pot utilitzar de forma fàcil, o com a comanda (amb totes les seves opcions).
Escollim una ciutat, i agafem les coordenades de Bagà: 42.2528016,1.8628564
db.cities.find({"loc": {"$near": [1.8628564, 42.2528016] }}) ... { "_id" : ObjectId("54579599e9cd9f37d7fd147e"), "name" : "Ripoll", "country" : "ES", "timezone" : "Europe/Madrid", "population" : 11057, "loc" : { "longitude" : 2.19033, "latitude" : 42.20064 } } ...
Per defecte dóna 100 valors. Si vull limitar la cerca:
db.cities.find({"loc": {"$near": [1.8628564, 42.2528016], "$maxDistance": 1 }}) db.cities.find({"loc": {"$near": [1.8628564, 42.2528016], "$maxDistance": 1 }}).limit(5) { "_id" : ObjectId("54579599e9cd9f37d7fd17cb"), "name" : "Bagà", "country" : "ES", "timezone" : "Europe/Madrid", "population" : 2178, "loc" : { "longitude" : 1.86098, "latitude" : 42.25289 } } ...
"$maxDistance": 1 -> significa 1 radian
Puc limitar el número de resultats amb limit(). Com a màxim donarà 100 resultats. El que és important, obligatori haver definit un index, si no no funciona.
db.runCommand({"geoNear": "cities", "near": [1.8628564, 42.2528016], "maxDistance": 1})
Obtenim la informació de la distància dels punts, i una estadística. Està ordenat per proximitat. No es pot utilitzar el pretty() perquè només retorna un objecte. Millor mirar-ho amb el Robomongo.
Ara farem servir un index de geolocalització avançat, i podrem fer servir metres en comptes de radians per calcular la proximitat.
db.cities.dropIndex("geo_idx") db.cities.ensureIndex({"loc": "2dspehere"}, {"name": "geo_idx"}) db.cities.getIndexes()
El format del find() tal com estava abans no funciona. L'hem de canviar de la següent manera:
db.cities.find({"loc": {"$near": { $geometry: { type: "Point", coordinates: [1.8628564, 42.2528016] }, "$maxDistance": 20000, "$minDistance": 0 }} }) { "_id" : ObjectId("54579599e9cd9f37d7fd17cb"), "name" : "Bagà", "country" : "ES", "timezone" : "Europe/Madrid", "population" : 2178, "loc" : { "longitude" : 1.86098, "latitude" : 42.25289 } } { "_id" : ObjectId("54579599e9cd9f37d7fd1443"), "name" : "Sant Julià de Cerdanyola", "country" : "ES", "timezone" : "Europe/Madrid", "population" : 1212, "loc" : { "longitude" : 1.89308, "latitude" : 42.2235 } } ...
La informació del punt no ha d'estar en format logn,lat, sinó en format GeoJSON. Ara puc demanar una proximitat de 2Km (20000m)
db.runCommand({ "geoNear": "cities", "near": {type: "Point", coordinates: [1.8628564, 42.2528016]}, "spherical": true, "maxDistance": 20000, "minDistance": 0 })
Podem veure com Castellar del Riu està a 16Km en línia recta.
Cerca basada en regions. Cercar objectes que estiguin delimitats en una regió. $geoWithin. Es tracta de que dins de $box li passem les dues coordenades que ens defineixen un rectangle. En aquest cas, cerco punts en la caixa definida entre Bagà i Perpinyà.
db.cities.find({ "loc": { "$geoWithin": { "$box" : [[1.8628564, 42.2528016],[2.9018408, 42.6980102]] } } }) ... { "_id" : ObjectId("54579599e9cd9f37d7fd26d6"), "name" : "Osséja", "country" : "FR", "timezone" : "Europe/Paris", "population" : 1673, "loc" : { "longitude" : 1.98192, "latitude" : 42.41383 } } { "_id" : ObjectId("54579599e9cd9f37d7fd28d5"), "name" : "Montesquiu d'Albera", "country" : "FR", "timezone" : "Europe/Paris", "population" : 1095, "loc" : { "longitude" : 2.88243, "latitude" : 42.51798 } }
I encara més complicat, podem definir un polígon de cerca. De fet, es troben bases de dades amb els polígons dels països, etc. És el mateix, però en comptes de $box posem $polygon.
Punts que es troben dins de Cuba.
db.cities.find({"loc": {"$geoWithin": {"$polygon": [[-82.268151,23.188611],[-81.404457,23.117271],[-80.618769,23.10598],[-79.679524,22.765303],[-79.281486,22.399202],[-78.347434,22.512166],[-77.993296,22.277194],[-77.146422,21.657851],[-76.523825,21.20682],[-76.19462,21.220565],[-75.598222,21.016624],[-75.67106,20.735091],[-74.933896,20.693905],[-74.178025,20.284628],[-74.296648,20.050379],[-74.961595,19.923435],[-75.63468,19.873774],[-76.323656,19.952891],[-77.755481,19.855481],[-77.085108,20.413354],[-77.492655,20.673105],[-78.137292,20.739949],[-78.482827,21.028613],[-78.719867,21.598114],[-79.285,21.559175],[-80.217475,21.827324],[-80.517535,22.037079],[-81.820943,22.192057],[-82.169992,22.387109],[-81.795002,22.636965],[-82.775898,22.68815],[-83.494459,22.168518],[-83.9088,22.154565],[-84.052151,21.910575],[-84.54703,21.801228],[-84.974911,21.896028],[-84.447062,22.20495],[-84.230357,22.565755],[-83.77824,22.788118],[-83.267548,22.983042],[-82.510436,23.078747],[-82.268151,23.188611]]}}}) ...{ "_id" : ObjectId("54579598e9cd9f37d7fce883"), "name" : "Yaguajay", "country" : "CU", "timezone" : "America/Havana", "population" : 42218, "loc" : { "longitude" : -79.23778, "latitude" : 22.32722 } } ...
Sessió 6. 11-nov-2014
Replicació a MongoDB
(no ens dóna temps de fer sharding).
Per fer la pràctica, com a mínim hauríem de ser 3. Es tracta de replicar dades entre varis servidors (nodes), que treballaran com una unitat de replicació.
Replica Sets (Cluster) és un grup d'instàncies de mongodb que mantenen la mateixa col.lecció de dades. Les dades es repliquen entre els nodes. Això millora la disponibilitat de les dades.
Les nostres aplicacions/driver interactuen amb un Replica Set. Les lectures es podran distribuir entre els diferents nodes (per ex, amb el criteri de proximitat geogràfica). Si cau un node, els altres nodes estan actius.
Hi ha un node que és el primari, que centralitza totes les dades d'escriptura (equivalent a insert, delete, update). Per defecte també rep totes les lectures.
Les operacions de replicació són asíncrones. Si llegim directament dels secundaris ens podem trobar dades inconsistents, que encara no estan replicades.
En tot servidor mongodb hi ha una bd que es diu local, que emmagatzema tota la informació dels replica set. Té una col.lecció (capped collection, que té el tamany màxim limitat) que es diu oplog. Els documents que hi ha a OPLOG són totes les instruccions (històric) que es fan en el primari, i que s'han de replicar en el secundari. És com una llista de les tasques que s'han de realitzar en els nodes secundaris. El tamany de les capped collection és per defecte el 5% del tamany lliure del disc, i això pot ser molt gran.
Els secundaris entre sí s'estan enviant continuament missatgets (heartbeats). Si el primari cau, els secundaris s'ho comenten, i un dels secundaris esdevindrà primari (als secundaris se'ls dóna una prioritat; també té en compte en la votació l'estat actual dels secundaris).
Pràctica 1 persona sola: hem de fer tres instàncies de mongo. Però és millor fer la pràctica en grup. Si ho faig jo sol, hauré de llençar tres instàncies amb tres ports diferents.
Pràctica. En grups de 3 o 4, arrencarem tres servidors de mongo (mongod) pel port 27018. Cadascun amb el seu DATAPATH. Arrencarem mongod amb el paràmetre --replSet, amb un nom que els identifica. En un Replica Set hi poden haver fins a 12 nodes.
La ruta on vam instal.lar mongo és: C:\Program Files\MongoDB 2.6 Standard. A dins hi ha data/db/ on es guarden les dades.
Escollim un nom del Replica Set per al nostre grup: rsfila2. Creem aquesta carpeta a data/. La mateixa carpeta l'han de crear els companys. Anem a arrencar els nodes (cada company arrenca el seu):
> mongod --port 27018 --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\rsfila2" --replSet rsfila2 --oplogSize 200
És important que tots els servidors diguin que volen treballar amb el mateix Replica Set. El nom del Replica Set no té perquè coincidir amb el nom de la carpeta. El oplog el volem amb un tamany per ex de 200MB, perquè per defecte seria el 5% del tamany lliure del disc.
Encara no està. Si llenço aquesta comanda, la meva instància encara no s'entera de què hi ha altres instàncies a la xarxa amb les quals em vull replicar. Falta que li presentem els altres membres del Replica Set. De moment el deixem engegat i va vomitant missatges de què no sap trobar els altres membres del Replica Set. Llença heartbeats, però no troba a d'altres nodes en la seva mateixa estructura.
Següent pas: ytiltizant la shell de mongo, ens connectarem al nostre servidor:
> mongo --port 27018 > use local > show databases admin (empty) local 0.078GB
Veig que la bd local comença a tenir informació.
La meva IP és al 192.168.20.218 la dels companyts 20.129, 20.132, 20.181
rs és un àlies del Replica Set. Per exemple, podem agregar membres.
> rs.help() > rs.status() { "startupStatus" : 3, "info" : "run rs.initiate(...) if not yet done for the set", "ok" : 0, "errmsg" : "can't get local.system.replset config from self or any seed (EMPTYCONFIG)" }
Només un dels nodes llençarà la sentència de configuració, i aquest tindrà tots els números de ser el primari.
Anem a crear un document JSON que serà la configuració del meu Replica Set, on constarà les IPs dels nodes.
config = { "_id": "rsfila2", "members": [ {"_id": 1, "host": "192.168.20.218:27018"}, {"_id": 2, "host": "192.168.20.129:27018"}, {"_id": 3, "host": "192.168.20.181:27018"}, {"_id": 4, "host": "192.168.20.132:27018", "priority": 0} ] }
Podem passar més tipus d'informació, com per exemple hi ha diferents tipus de secundari. Per ex, nodes de prioritat 0 (priority), que no volem que mai siguin primaris; nodes ocults (mai es faran lectures d'ell, només existeixen com a backup); node delayed (mai serà primari, ni es farà lectura d'ell, i és delayed: la replicació es fa després d'una hora o el que vulguem). El node delayed va molt bé per recuperació de desastres (necessitem un optlog molt llarg).
Ho executem a la shell (de moment només és una variable que es diu config). rs.initiate(config)
i ara els logs canvien, es troben els membres els uns als altres. Comencen a parlar i s'estan votant, de manera que s'escull un primari.
... 014-11-11T10:25:37.338+0100 [initandlisten] connection accepted from 192.168.2 132:49309 #22 (9 connections now open) 014-11-11T10:25:37.338+0100 [initandlisten] connection accepted from 192.168.2 132:49310 #23 (10 connections now open) 014-11-11T10:25:38.773+0100 [rsHealthPoll] replSet member 192.168.20.181:27018 s now in state SECONDARY 014-11-11T10:25:38.773+0100 [rsHealthPoll] replSet member 192.168.20.132:27018 s now in state SECONDARY 014-11-11T10:25:47.200+0100 [conn8] end connection 192.168.20.129:50189 (9 con ctions now open) 014-11-11T10:25:47.200+0100 [initandlisten] connection accepted from 192.168.2 129:50196 #24 (11 connections now open) ...
> rs.status() rsfila2:PRIMARY> rs.status() { "set" : "rsfila2", "date" : ISODate("2014-11-11T09:30:12Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "192.168.20.218:27018", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2046, "optime" : Timestamp(1415697913, 1), "optimeDate" : ISODate("2014-11-11T09:25:13Z"), "electionTime" : Timestamp(1415697924, 1), "electionDate" : ISODate("2014-11-11T09:25:24Z"), "self" : true }, { "_id" : 2, "name" : "192.168.20.129:27018", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 298, "optime" : Timestamp(1415697913, 1), "optimeDate" : ISODate("2014-11-11T09:25:13Z"), "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"), "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:15Z"), "pingMs" : 0, "syncingTo" : "192.168.20.218:27018" }, { "_id" : 3, "name" : "192.168.20.181:27018", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 296, "optime" : Timestamp(1415697913, 1), "optimeDate" : ISODate("2014-11-11T09:25:13Z"), "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"), "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:25Z"), "pingMs" : 0, "syncingTo" : "192.168.20.218:27018" }, { "_id" : 4, "name" : "192.168.20.132:27018", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 296, "optime" : Timestamp(1415697913, 1), "optimeDate" : ISODate("2014-11-11T09:25:13Z"), "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"), "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:25Z"), "pingMs" : 0, "syncingTo" : "192.168.20.218:27018" } ], "ok" : 1 }
> db.oplog.rs.find().pretty() de moment no hi ha res.
Ara jo, com a primari, importaré la base de dades tweets. En el oplog s'escriurant totes les tasques que s'hauran de fer, i això es replicarà en els altres nodes.
C:\> mongoimport --host localhost:27018 --db twitter --collection tweets --type json --file "D:\tweets.json" --drop --stopOnError
> rsfila2:PRIMARY> db.tweets.count() 51428 > db.oplog.rs.find().pretty() rsfila2:PRIMARY> show databases admin (empty) geography 0.078GB local 0.328GB twitter 0.203GB > db.oplog.rs.find().pretty()
Ara està ple d'informació que són les dades que s'han de replicar. En el servidor dels companys ja s'ha creat la base de dades i tenen les dades replicades.
Per tal de què els secundaris també puguin llegir, en els secundaris s'ha de fer:
> rs.slaveOk()
Els secundaris no poden escriure, només poden llegir. Només el primari pot escriure:
> db.tweets.insert({"name": "Joan"})
rsfila2:PRIMARY> db.oplog.rs.count() 52013
rsfila2:PRIMARY> db.oplog.rs.find().pretty() ... { "ts" : Timestamp(1415699916, 19), "h" : NumberLong("3970298272155406135"), "v" : 2, "op" : "i", "ns" : "twitter.tweets", "o" : { "_id" : ObjectId("5461ddcc90c824c08d77197a"), "text" : "RT @ayatquran: Sesungguhnya shalat itu adalah kewajiban yang ditentukan waktunya atas orang-orang yang b eriman (4:103)", "in_reply_to_status_id" : null, "retweet_count" : null, "contributors" : null, "created_at" : "Thu Sep 02 18:11:25 +0000 2010", "geo" : null, "source" : "<a href=\"http://www.ubertwitter.com/bb/download.php\" rel=\"nofollow\">ÜberTwitter</a>", "coordinates" : null, "in_reply_to_screen_name" : null, "truncated" : false, "entities" : { "user_mentions" : [ { "indices" : [ 3, 13 ], "screen_name" : "ayatquran", "name" : "Ayat Quran", "id" : 56593724 } ], "urls" : [ ], "hashtags" : [ ] }, "retweeted" : false, "place" : null, "user" : { "friends_count" : 544, "profile_sidebar_fill_color" : "2c1e7d", "location" : "ÜT: 0.504168,101.450489", "verified" : false, "follow_request_sent" : null, "favourites_count" : 2, "profile_sidebar_border_color" : "3d0d3d", "profile_image_url" : "http://a0.twimg.com/profile_images/1110579656/for_20twit_normal.jpg", "geo_enabled" : true, "created_at" : "Sat Dec 26 20:13:30 +0000 2009", "description" : "hidup yg menyenangkn bersama", "time_zone" : "Jakarta", "url" : "http://null", "screen_name" : "indahrahenni", "notifications" : null, "profile_background_color" : "101e52", "listed_count" : 1, "lang" : "en", "profile_background_image_url" : "http://a1.twimg.com/profile_background_images/116008894/the_karate_kid_9 .jpg", "statuses_count" : 1785, "following" : null, "profile_text_color" : "8c7142", "protected" : false, "show_all_inline_media" : false, "profile_background_tile" : true, "name" : "indah anugrah", "contributors_enabled" : false, "profile_link_color" : "0a040a", "followers_count" : 100, "id" : 99567001, "profile_use_background_image" : true, "utc_offset" : 25200 }, "favorited" : false, "in_reply_to_user_id" : null, "id" : NumberLong("22819399000") } } ...
Ens dóna informació de totes les tasques que s'han fet, i de totes les tasques pendents.
creem la col.lecció logs en el PRIMARY
> use logs > for (var i=0; i<=10000; i++) { db.logs.insert({"_id": i, "time": new ISODate()}); }
i ara mirem el oplog per veure com està l'estat de totes aquestes insercions
> use local rsfila2:PRIMARY> db.oplog.rs.find().skip(60000).pretty() { "ts" : Timestamp(1415703957, 997), "h" : NumberLong("6946955576797341749"), "v" : 2, "op" : "i", "ns" : "logs.logs", "o" : { "_id" : 8005, "time" : ISODate("2014-11-11T11:05:57.508Z") } } { "ts" : Timestamp(1415703957, 998), "h" : NumberLong("5184908651075962710"), "v" : 2, "op" : "i", "ns" : "logs.logs", "o" : { "_id" : 8006, "time" : ISODate("2014-11-11T11:05:57.508Z") } }
Operacions que no siguin de tipus i (inserció):
db.oplog.rs.find({"op": {"$ne": "i"}}) { "ts" : Timestamp(1415697913, 1), "h" : NumberLong(0), "v" : 2, "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" }
"op" : "n" és una operació de tipus new, d'inicialització.
rsfila2:PRIMARY> use logs switched to db logs rsfila2:PRIMARY> db.logs.drop() true > db.dropDatabase() { "dropped" : "logs", "ok" : 1 }
Hem eliminat la bd. Si hi hagués algun secundari amb replicació retardada, encara conservaria les dades.
rsfila2:PRIMARY> db.oplog.rs.find({"op": {"$ne": "i"}}).pretty() { "ts" : Timestamp(1415697913, 1), "h" : NumberLong(0), "v" : 2, "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } } { "ts" : Timestamp(1415704284, 1), "h" : NumberLong("-2663097432869229176"), "v" : 2, "op" : "c", "ns" : "logs.$cmd", "o" : { "drop" : "logs" } } { "ts" : Timestamp(1415704413, 1), "h" : NumberLong("1820014921288848938"), "v" : 2, "op" : "c", "ns" : "logs.$cmd", "o" : { "dropDatabase" : 1 } }
Veig que hi ha les operacions de tipus c, que són les comandes, que es corresponen al db.logs.drop() i al db.dropDatabase().
Faig Ctrl-C (mato el primari), i automàticament els tres secundaris entren en un procés de votació per tal de saber quin serà el nou primari. En fer un status se sabrà quin és el nou primari. El primari passa a ser la IP 20.181.
MongoDB Seguretat
Mirar els apunts. Per defecte MongoDB treballa en mode obert i no té autenticació, tal com hem fet al llarg del curs. Si volem treballar amb autenticació hem d'anar al fitxer de configuració
security: authorization: enabled
(per defecte és disabled). Quan habilitem l'autenticació, hem de crear el primer usuari. Només et deixa connectar-te des de la màquina local (localhost exception) per tal de poder crear el primer usuari. Un cop creat el primer usuari, aquesta excepció deixa de funcionar. Per tant, s'ha de crear els usuaris amb el PERMISOS SUFICIENTS. En cas de desastre no passa res: es torna a modificar el fitxer de configuració amb seguretat deshabilitada.
> use admin > sb.createUser(...)
creat per Joan Quintana Compte, octubre 2014