Introdução Hoje em dia, fala-se muito sobre os bancos de dados NoSQL. Porém, poucos ainda conhecem esse conceito, que não é novo. Neste curso, explicarei o que são esses bancos de dados tão falados ultimamente, onde usar e por que usá-los. O foco será em um banco de dados largamente utilizado, que é o MongoDB, e demonstrarei exemplos de sua utilização real com o PHP, outra linguagem para Web largamente utilizada. NOSQL O que é? Por que usar? Onde usar? Tipos de armazenamento Cases reais Por que usar? O termo NoSQL foi utilizado pela primeira vez em 1998 como nome de um banco de dados relacional de código aberto que não possuía um interface SQL. Seu autor, Carlo Strozzi, alega que o movimento NoSQL "é completamente distinto do modelo relacional e portanto deveria ser mais apropriadamente chamado "NoREL" ou algo que produzisse o mesmo efeito". Porém, o termo só voltou a ser assunto em 2009 por um funcionário do Rackspace, Eric Evans, quando Johan Oskarsson da Last.fm queria organizar um evento para discutir bancos de dados open source distribuídos. NoSQL são diferentes sistemas de armazenamento que vieram para suprir necessidades nas quais os bancos de dados tradicionais (relacionais) são ineficazes. Muitas dessas bases apresentam características interessantes, como: alta performance, escalabilidade, replicação, suporte a dados estruturados, grafos e sub-colunas. O NoSQL surgiu da necessidade de uma performance superior e de alta escalabilidade. Os atuais bancos de dados relacionais são muito restritos a isso, sendo necessária a distribuição vertical de servidores, ou seja, quanto mais dados, de mais memória e de mais disco um servidor precisa. O NoSQL tem uma grande facilidade na distribuição horizontal, ou seja, mais dados, mais servidores, não necessariamente de alta performance. Um grande utilizador desse conceito é o Google, que usa computadores de pequeno e médio porte para a distribuição dos dados. Essa forma de utilização é muito mais eficiente e econômica. Além disso, os bancos de dados NoSQL são muito tolerantes a erros. No caso dos bancos NoSQL, toda a informação necessária estará agrupada no mesmo registro, ou seja, em vez de você ter o relacionamento entre várias tabelas para formar uma informação, ela estará em sua totalidade no mesmo registro. Onde usar? Atualmente, temos vários bancos NoSQL que podem resolver diversos problemas. De qualquer forma, eles não são a chave para TODOS os problemas. Ainda existem cenários em que os bancos relacionais são mais indicados, visto que eles possuem propriedades ACID. Logo, são melhores em cenários em que os dados são muitíssimo importantes e não pode haver nenhuma quebra de referência. Ou seja, não indicaria, ainda, algum banco NoSQL para sistemas de transações financeiras, por exemplo. Mas se o seu sistema é alguma rede social ou algum site que necessite de alta disponibilidade ou escalabilidade, com certeza lhe indicaria um banco NoSQL. Entretanto, não precisamos mudar todo o sistema para algum banco NoSQL. Podemos utilizar um banco NoSQL e um relacional em conjunto. Como muitos já fazem, mas nem percebem. Por exemplo: um sistema que utiliza cache com certeza está usando um banco NoSQL no cache, como o mais conhecido Memcached. A API Storage do HTML5 também utiliza um sistema de banco NoSQL do tipo chave-valor. Tipo de armazenamento Existem diversos tipos de armazenamento, no qual cada um trata os dados de uma forma diferente e que pode ser mais específico para o objetivo desejado. Os tipo de armazenamento são: Wide Column Store/Column Families, Document Store, Key Value/Tuple Store, Eventually Consistent Key Value Store, Graph Databases, Object Databases, Grid Database Solutions, XML Databases. Lista retirada de http://nosql-database.org/ Key/Value Store Esse é o tipo de banco de dados NoSQL mais simples. O conceito dele é uma chave e um valor para essa chave, mas ele é o que aguenta mais carga de dados. Estes tipos de bancos de dados são o que tem a maior escalabilidade: • Berkeley DB • Tokyo Cabinet • Kyoto Cabinet • Project Voldermort • MemcacheDB • SimpleBD • Redis • Riak Wide Columns Store Fortemente inspirados pelo BigTable do Google, suportam várias linhas e colunas, e também permitem subcolunas. Além do BigTable do Google, outros que usam essa tecnologia são: • HBase(Apache) • HiperTable • Cassandra(Apache) Document Store Baseados em documentos XML ou JSON, podem ser localizado pelo seu id único ou por qualquer registro que tenham no documento: • CouchDB(Apache) • MongoDB • RavenDB Graph Store Com uma complexibilidade maior, esses bancos de dados guardam objetos, e não registros, como os outros tipos de NoSQL. A busca desses itens é feita pela navegação destes objetos: • Neo4J • InfoGrid • HyperGraphDB Column Oriented Store Esses são bancos de dados relacionais que têm características do NoSQL. A principal diferença deles é que os dados são armazenados em colunas, ajudando na escalabilidade: • Vertica • MonetDB • LucidDB • Infobright • Ingres/Vectorwise Na imagem abaixo, podemos ver um gráfico demonstrando a diferença entre o tamanho da base de dados e a complexidade dos seus dados. Assim, podemos perceber que os bancos do tipo chave-valor conseguem aguentar mais dados, sendo que seus dados são mais simples, enquanto que os bancos do tipo grafo aguentam menos dados, e seus dados são mais complexos. Cases reais Um case real interessante é o da Netflix que utiliza 3 bancos NoSQL: SimpleDB, HBase e Cassandra. O SimpleDB é uma implementação NoSQL da Amazon, e por isso foi escolhido, já que a empresa utiliza o serviço AWS da Amazon. Ele escreve réplicas automáticas nas zonas disponíveis dentro de uma região. É altamente durável e também possui características, além de uma interface de chave-valor simples, como: vários atributos chave de linha, operações em lote, as leituras consistentes etc. O HBase é profundamente ligado ao Hadoop e pode escrever consultas em tempo real, apesar de sacrificar um pouco a disponibilidade pela consistência dos dados. Cassandra foi a escolhida pela escalabilidade e pelo poder de replicar dados assincronamente através de múltiplas regiões geográficas. Pode escalar dinamicamente adicionando mais servidores sem a necessidade de re-shard ou reiniciar. Como se pode ver, cada banco vem para suprir alguma necessidade específica. Esse foi um exemplo de como uma empresa pode utilizar vários bancos NoSQL para tratar de assuntos específicos. Porém, podemos usar facilmente apenas um para resolver nossos problemas. O MongoDB, que será nosso banco de estudo, é usado pelas seguintes empresas em produção: • Craigslist • Shutterfly • foursquare • bit.ly • SourceForge • Disney • MTV Networks • GitHub • Justin.tv • CollegeHumor • SugarCRM • GrooveShark • CartolaFC Podemos ver mais empresas utilizando-o emhttp://www.mongodb.org/display/DOCS/ Production+Deployments MongoDB - Introdução O MongoDB é um dos bancos NoSQL mais utilizados, pela sua facilidade de instalação, sua documentação e os diversos drivers para inúmeras linguagens de programação. Ele é um banco de dados orientado a documentos, escalável, livre de esquema, de alto desempenho e código aberto escrito em C++. Algumas funcionalidades interessantes do MongoDB são: • orientação a documentos(JSON/BSON) • suporte a index • replicação e alta disponibilidade • auto-sharding • map/reduce GridFS • suporte comercial da 10gen. Suas queries são simples e ricas. E seu map/reduce é bem flexível. Quando utilizamos, vemos a grande diferença de velocidade entre o MongoDb e os outros bancos relacionais. Isso acontece principalmente por ele guardar os dados na memória RAM, persistindo-o no disco rígido também. Por isso, é altamente aconselhável sempre usar um segundo nó de réplica para, caso o servidor desligue, você não perder seus dados. Para a maioria dos desenvolvedores que vem do mundo relacional, o MongoDB parecerá estranho, mas com o tempo é possível notar como ele é mais simples que a grande maioria dos SGBDs. A grande diferença, que para mim realmente é uma vantagem, é não possuir schema e não tentar representar um mundo n-dimensional com apenas 2 dimensões: coluna e linha. No MongoDB, o conjunto de dados não se chama tabelas, mas sim coleção. Essa coleção possui uma estrutura de JSON, como no seguinte exemplo: usuario{ nome: "Jean", apelido: "Suissa", idade: 26, telefone:{celular: 99998888, residencial:44445555}} Como podemos ver nesse exemplo, além de possuir os campos nome, apelido e idade, o campo celular é uma lista de valores. Nossos documentos JSON podem conter outros documentos JSON dentro, e assim por diante. Você irá controlar tudo isso via programação. Isso se diferencia bastante do modelo relacional, no qual todos os registros necessariamente precisam ter os mesmos campos, mesmo que sejam NULOS. Já no MongoDB você apenas acrescentará campos nos registros que realmente precisam. E, como havia dito anteriormente, o programador tem que ter muito mais cuidado com esse tipo de coisa para depois não se perder em suas estruturas. Porém, não pense que esse modelo NOSQL serve para todos os casos. Os casos em que ele serve perfeitamente são: sistemas de logs, sistemas que não necessitam de controle de transação, que necessitam de alta escalabilidade e que não possuem dados críticos. Uma dica que posso dar sobre como escolher o MongoDB para um projeto é pensar se o seu sistema terá muito mais leituras do que escritas concorrentes. Por exemplo: em um e-commerce, não haverá quase nenhum cliente escrevendo em cima de algum produto, porém haverá muitos clientes lendo esses registros. Esse é um caso clássico para se utilizar o MongoDb. Outro aspecto que pode modificar sua modelagem no MongoDB é pensar como suas informações serão lidas pela sua aplicação; nesse caso, você modelará seu banco pensando em agrupar suas informações para que sejam buscadas apenas uma vez e que o retorno dessas informações seja suficiente para a maioria das atividades executadas na aplicação. Instalação 1. 2. 3. 3.1.1 - Windows 3.1.2 - Linux 3.1.3 - MacOS Windows Faça o download para o Windows em http://www.mongodb.org/display/DOCS/ Quickstart+Windows Dezip o conteúdo do zip baixado em alguma pasta. Ex.: C:\mongo Crie 2 pastas no seu C:\, pois o MongoDB utiliza como padrão essas 2 pastas. C:\> mkdir \data C:\> mkdir \data\db Caso você queira utilizar pastas diferentes, use o atributo --dbpath quando iniciar o serviço do Mongo no seu console. Para você iniciar o seu Mongo, vá até o console e digite: • cd mongo\bin • mongod Após iniciado o serviço, você pode abrir outro console e ir na mesma pasta bin e rodar: • mongo Com isso, você estará rodando o cliente do MongoDB e já poderá testá-lo com seus comandos. Agora baixe a extensão do Mongo para PHP em https://github.com/mongodb/mongo-phpdriver/downloads Baixe a versão correta para seu Windows e coloque a dll na pasta das suas extensões de PHP. Após isso, abra o seu php.ini e adicione a seguinte linha: • extension=php_mongo.dll Linux Para instalar "automagicamente" o MongoDB, basta instalá-lo via apt-get, mas antes precisamos atualizá-lo: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10 Crie um arquivo /etc/apt/sources.list.d/10gen.list e adicione essa linha: deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen Agora só atualizamos: sudo apt-get update Para instalar seguimos a velha fórmula: • $ sudo apt-get install mongodb-10gen Agora instalaremos a extensão do Mongo para o PHP. • $ sudo pecl install mongo Crie as 2 pastas padrão que o Mongo utiliza com os seguintes comandos: $ sudo mkdir -p /data/db/ $ sudo chown `id -u` /data/db (caso não consiga. pode usar o chmod 777 -Rf na pasta / data, mas não é aconselhável) • $ sudo pico /etc/php5/apache2/php.ini • extension=mongo.so Adicione a seguinte linha ao seu php.ini: Reinicie seu Apache e confira se a extensão está rodando. • $ sudo /etc/init.d/apache2 restart Para conferir se a extensão está rodando, crie um arquivo php com o seguinte conteúdo: <?php phpinfo(); ?> Procure por mongo e veja se ele está devidamente instalado. Caso não consiga instalar via apt-get, pode baixar diretamente do site em:http:// www.mongodb.org/downloads Após a extração dos arquivos em uma pasta qualquer, você deve prestar atenção: quando você chama o binário mongo ou mongod, terá que fazer com a url completa da pasta. Para você rodar o serviço do Mongo, basta rodar o seguinte comando: • mongod Em um outro terminal, você irá rodar o cliente do Mongo com o seguinte comando: • mongo MacOS Para instalar "automagicamente" o MongoDB, basta instalá-lo via apt-get: $ brew update $ brew install mongodb Ou se você usa ports: • $ sudo port install mongodb Agora, instalaremos a extensão do Mongo para o PHP. • $ sudo pecl install mongo Crie as 2 pastas padrão que o Mongo utiliza com os seguintes comandos: $ sudo mkdir -p /data/db/ $ sudo chown `id -u` /data/db (caso não consiga, pode usar o chmod 777 -Rf na pasta / data, mas não é aconselhável) $ sudo pico /etc/php5/apache2/php.ini (ou onde seu php.ini estiver) Adicione a seguinte linha ao seu php.ini: • extension=mongo.so Reinicie seu Apache e confira se a extensão está rodando. • $ sudo /etc/init.d/apache2 restart (ou restart via MAMP) Para conferir se a extensão está rodando, crie um arquivo php com o seguinte conteúdo: <?php phpinfo(); ?> Procure por mongo e veja se ele está devidamente instalado. Caso não consiga instalar via port ou brew, pode baixar diretamente do site em:http:// www.mongodb.org/downloads Após a extração dos arquivos em uma pasta qualquer, você deve prestar atenção: quando você chama o binário mongo ou mongod, terá que fazer com a url completa da pasta. Para você rodar o serviço do Mongo, basta rodar o seguinte comando: • mongod Em um outro terminal, você irá rodar o cliente do Mongo com o seguinte comando: • mongo Comandos 1. 2. 3. 4. 5. 6. 3.2.1 - Coleção 3.2.2 - Inserção 3.2.3 - Consulta 3.2.4 - Alteração 3.2.5 - Exclusão 3.2.6 - Mapeamento SQL para MongoDB Coleção Antes de criarmos uma coleção, precisamos criar uma Database e, para isso, usaremos o seguinte comando: use nome_database Caso a Database já exista, o Mongo apenas a setará como default. Para listarmos as databases criadas, usamos o seguinte comando: • show dbs Para criarmos uma coleção, precisamos apenas usá-la: • db.nome_colecao.insert({a:1}) Sempre usaremos, para cada comando, o início db, que será o atalho para a database que escolhemos inicialmente com o comando use. A coleção só existirá após algum dado ser inserido nela. Para listarmos as coleções, usamos o seguinte comando: • show collections Para deletarmos a coleção, usamos: • db.nome_colecao.drop() Se quisermos apenas limpar a coleção sem apagá-la, usamos: • db.nome_colecao.remove() Caso queiramos criar uma collection sem utilizá-la inicialmente, podemos usar o seguinte comando: • db.createCollection("minha_collection") Inserção Para inserirmos algum dado, sempre utilizamos um documento no formato JSON. Usaremos o comando insert() da seguinte forma: db.professores.insert({nome: ?Jean?, idade:26, disciplinas:[?PHP?, ? NOSQL?], sexo: ?m?, created : new Date('12/21/2012')}) Ou também podemos criar um JSON com os dados e passá-lo como parâmetro para a função, como no exemplo a seguir: json = {nome: ?Jean?, idade:26, disciplinas:[?PHP?, ?NOSQL?], sexo: ?m?, created : new Date('12/21/2012')} db.professores.insert(json) Consulta Para fazermos consultas no MongoDB, utilizaremos 2 funções: • find() • findOne() Possuímos a seguinte sintaxe para fazermos consultas: db.colecao.find({clausulas, campos}) E, para usarmos como WHERE, são necessários alguns operadores especiais que listo na próxima página. Caso seja necessária uma consulta por string mais complexa, podemos usar REGEX. db.professores.find( { nome : /J*/i } );<br />db.professores.find( { name : { $regex : 'J*'} } ); Agora veremos um paralelo entre algumas funções de seleção da SQL e sua alternativa para o Mongo: select count(*) from professores<br />db.professores.count() select count(*) from professores where idade > 30<br / >db.professores.count({idade:{$gt: 30}}) select nome from professores where idade < 18<br / >db.professores.find({idade: {$lt: 18}})) select nome, idade from professores<br />db.professores.find(null, {nome:1, idade:1} ) select nome, idade from professores where idade >= 18<br / >db.professores.find({idade:{$gte: 18}}, {nome:1, idade:1} ) select * from professores order by nome ASC <br / >db.professores.find().sort({nome:1}) select * from professores order by idade DESC <br / >db.professores.find().sort({idade:-1}) select * from professores order by nome ASC limit 0,2 <br / >db.professores.find().sort({nome:1}).limit(2) select * from professores limit 2 offset 10<br / >db.professores.find().limit(2).skip(10) Entretanto, qualquer seleção com find() irá retornar um cursor do MongoDB no qual você precisará iterar para retirar suas informações, enquanto que utilizando o findOne() você retornará apenas 1 registro. select nome, idade from professores LIMIT 0,1<br / >db.professores.findOne(null, {nome:1, idade:1} ) Operadores Condicionais No MongoDB, utilizamos operadores especiais para criar nossos critérios de busca. Abaixo, a lista desses operadores: • < ou $lt • <= ou $lte • > ou $gt • >= ou $gte • $all - retorna o objeto no qual todos os valores foram encontrados • $exists - retorna o objeto caso uma chave exista • $mod - retorna o objeto quando o módulo de um valor foi encontrado • $ne- retorna o objeto no qual o valor não foi encontrado • $in- retorna o objeto se o valor foi encontrado • $nin- retorna o objeto se nenhum dos valores foi encontrado • $nor- retorna o objeto caso a cláusula negação do OU for verdadeira • $or- retorna o objeto caso a cláusula OU for verdadeira • $and- retorna o objeto caso a cláusula E for verdadeira • $size • $type - retorna o objeto caso a chave seja do tipo especificado para conferir a lista dos tipos e seus valores. Acesse a lsta dos tipos e seus valores inserir bloco Ordenação Para ordenarmos uma consulta no MongoDB, precisamos apenas utilizar a função sort(), como no exemplo a seguir: db.professores.find({}).sort({email: 1}); Utilizamos o valor 1 para ordenação ASCENDENTE e -1 para ordenação DESCENDENTE. Skip e Limit Para pularmos o início do retorno dos nossos registros, usamos a função skip() e, para limitarmos a quantidade de registros retornados, utilizamos a função limit(), como demonstrado abaixo: db.professores.find().skip(20).limit(10); Cursores Qualquer busca feita com o find() irá retornar um cursor e, para retirarmos os valores dele, precisamos iterar nele como no exemplo abaixo: var cur = db.professores.find(); cur.forEach( function(x) { print(tojson(x))}); Alteração Para realizarmos alterações, podemos fazer uma consulta que irá retornar um JSON. Podemos manipulá-lo e mandar o Mongo para salvar o objeto com a modificação. Podemos fazer isso da seguinte forma: Para realizarmos uma alteração no MongoDB, podemos utilizar 2 funções: • save() • update() A diferença entre o save() e o update() é que a função save() irá criar o registro caso ele não encontre nenhum que bata com o seu critério de busca. Porém, isso também pode ser feito com o parâmetro upsert na função update(). A sintaxe para a função update() é a seguinte: db.collection.update( criterio, obj, upsert, multi ) • criterio - query que irá selecionar o registro para alterar • objNew - objeto modificado ou operadores de alteração • upsert - caso o registro não exista, ele será inserido • multi - caso encontre mais de um registro pelo criterio, ele atualiza todos esses registros Caso você não passe o parâmetro multi, ele só atualiza um único registro. Exemplo da diferença entre o update() e o save(): db.minhaCollection.save(x); db.minhaCollection.update( { _id: x._id }, x, /*upsert*/ true ) A forma mais simples de se alterar um registro é pesquisando-o com findOne, editandoo e, após isso, salvando. Caso precise apenas salvar, simplesmente aconselho o uso da função save(), como a seguir: professor = db.professores.findOne( { nome : "Jean" } );<br / >professor.idade = 28;<br />db.professores.save(professor); Mas também podemos alterar diretamente com o update: professor = db.professores.findOne( { nome : "Jean" } );<br / >professor.idade = 27;<br />db.professores.update({ nome : "Jean" }, {$set: {idade: 28} }); Fora isso, podemos usar diversos operadores na query, exemplo: db.professores.update( { nome:"Jean" }, { $inc: { visitas: 1 }, $set: { idade: 28 } } ); Operadores de Modificação Os operadores de modificação servem para que você possa alterar valores nos registros diretamente na query do MongoDB. Como vimos anteriormente, para atualizar minha idade de 27 para 28 anos, usamos o operador $set. $inc { $inc : { campo : valor } } Incrementa um valor no campo; caso o campo não exista, ele irá criar o campo e setar o valor. Para decrementar, basta passar um valor negativo. $set { $set : { campo : valor } } Seta o valor do campo. $unset { $unset : { campo : 1} } Deleta o campo. $push { $push : { campo : valor } } Adiciona um valor ao campo se o campo for um array existente. Caso contrário, transforma o campo em um array com o valor como índice. Porém, se o campo existe e não for um array, irá retornar um erro. Arrays múltiplos podem ser alterados em uma única operação separando os pares de campo : valor por vírgula. { $push : { campo1 : valor, campo2 : valor2 } } $pushAll { $pushAll : { campo : valor_array } } Adiciona cada valor dentro de valor_array para o campo se o campo for um array existente. Caso contrário, seta o campo com o valor_array. Se o campo estiver presente mas não for um array, irá retornar um erro. $addToSet e $each { $addToSet : { campo : valor } } Adiciona um valor para um array somente se o valor ainda não estiver no array, caso o campo seja um array existente. Caso contrário, seta o campo para o array se o campo não existir. Se o campo estiver presente e não for array, irá retornar um erro. Para adicionar uma lista de vários valores, use o qualificador $each: { $addToSet : { a : { $each : [ 3 , 5 , 6 ] } } } $pop { $pop : { campo : 1 } } Remove o último elemento do array campo. $pull { $pull : { campo : _valor } } Remove todas as ocorrências de _valor no campo se o campo for um array. Se o campo estiver presente mas não for array, irá retornar um erro. Para acertar um valor exato, você também pode usar expressões como essas: { $pull : { campo : {<criterio>} } } //remove elementos do array que batam com o seu critério { $pull : { campo : {campo2: valor} } } //remove os elementos do array com campo2 possuindo o valor { $pull : { campo : {$gt: 3} } } //remove elementos do array que forem maior que 3 $pullAll { $pullAll : { campo : valor_array } } Remove todas as ocorrências de cada valor no valor_array do campo se o campo for um array. Se o campo existir mas não for um array, irá retornar um erro. $rename { $rename : { nome_campo_antigo : nome_campo_novo } } Renomeia o nome_campo_antigo para nome_campo_novo. Exclusão Para excluir dados, utilizamos a mesma lógica, porém com a função remove(). db.professores.remove(); // exclui tudo<br / >db.professores.remove({nome: "Jean"}); // exclui todos os professores com nome = Jean O jeito mais eficiente de remover um documento é utilizando seu ObjectID, exemplo: professor = db.professores.findOne({nome: "Jean"}) db.professores.remove({_id: professor._id}); Mapeamento SQL para MongoDB Vamos ver alguns exemplos de SQL e sua tradução para o PHP usando MongoDB: INSERT INTO USERS VALUES(1,1) $db->users->insert(array("a" => 1, "b" => 1)); SELECT a,b FROM users $db->users->find(array(), array("a" => 1, "b" => 1)); SELECT * FROM users WHERE age=33 $db->users->find(array("age" => 33)); SELECT a,b FROM users WHERE age=33 $db->users->find(array("age" => 33), array("a" => 1, "b" => 1)); SELECT a,b FROM users WHERE age=33 $db->users->find(array("age" => 33), array("a" => 1, "b" => 1)); SELECT a,b FROM users WHERE age=33 ORDER BY name $db->users->find(array("age" => 33), array("a" => 1, "b" => 1))->sort(array("name" => 1)); SELECT * FROM users WHERE age>33 $db->users->find(array("age" => array('$gt' => 33))); SELECT * FROM users WHERE age<33 <br />$db->users->find(array("age" => array('$lt' => 33))); SELECT * FROM users WHERE name LIKE "%Joe%" $db->users->find(array("name" => new MongoRegex("/Joe/"))); SELECT * FROM users WHERE name LIKE "Joe%" $db->users->find(array("name" => new MongoRegex("/^Joe/"))); SELECT * FROM users WHERE age>33 AND age<=40 $db->users->find(array("age" => array('$gt' => 33, '$lte' => 40))); SELECT * FROM users ORDER BY name DESC $db->users->find()->sort(array("name" => -1)); CREATE INDEX myindexname ON users(name) $db->users->ensureIndex(array("name" => 1)); CREATE INDEX myindexname ON users(name,ts DESC) $db->users->ensureIndex(array("name" => 1, "ts" => -1)); SELECT * FROM users WHERE a=1 and b='q' $db->users->find(array("a" => 1, "b" => "q")); SELECT * FROM users LIMIT 10 SKIP 20 $db->users->find()->limit(10)->skip(20); SELECT * FROM users WHERE a=1 or b=2 $db->users->find(array('$or' => array(array("a" => 1), array("b" => 2)))); SELECT * FROM users LIMIT 1 $db->users->find()->limit(1); EXPLAIN SELECT * FROM users WHERE z=3 $db->users->find(array("z" => 3))->explain() SELECT DISTINCT last_name FROM users $db->command(array("distinct" => "users", "key" => "last_name")); SELECT COUNT(*) FROM users $db->users->count(); SELECT COUNT(*) FROM users where AGE > 30 $db->users->find(array("age" => array('$gt' => 30)))->count(); SELECT COUNT(AGE) from users $db->users->find(array("age" => array('$exists' => true)))->count(); UPDATE users SET a=1 WHERE b='q' $db->users->update(array("b" => "q"), array('$set' => array("a" => 1))); UPDATE users SET a=a+2 WHERE b='q' $db->users->update(array("b" => "q"), array('$inc => array("a" => 2))); DELETE FROM users WHERE z="abc" $db->users->remove(array("z" => "abc")); Sharding O MongoDB suporta uma arquitetura automatizada sharding/partição, permitindo escalar horizontalmente em vários nós. Para aplicações que superam os recursos de um servidor de banco de dados único, o MongoDB pode converter para um cluster sharded, gerenciando automaticamente o failover e o balanceamento de nós, com poucas mudanças ou não para o código do aplicativo original. Sharding é o particionamento de dados entre vários computadores de uma forma de preservação da ordem. Para dar um exemplo, vamos imaginar sharding como uma coleção de usuários pelo seu estado de residência. Em uma visão simplista, se designar três máquinas como servidores de nossos cacos, os usuários podem ser divididos por máquina da seguinte forma: Dividindo a quantidade de estados existentes pelo número de shards existentes. Pensando na quantidade de dados existentes por estado para fazer um balanceamento correto. Nosso aplicativo se conectará ao cluster sharded através de um processo Mongos, que encaminha as operações para o(s) shard(s) apropriado(s). Dessa forma, o aglomerado MongoDB sharded se parecerá como um único servidor lógico para a nossa aplicação. Desde que nossos documentos estejam organizados em uma forma de preservação da ordem, todas as operações que especificam o estado de residência serão encaminhadas apenas para os nós que contêm esse estado. Como o nosso foco é trabalhar com PHP, não me aprofundarei nesta sessão, pois o assunto é muito extenso. Configuração Componentes Sharding Primeiro configuramos os fragmentos individuais (mongod's), depois os servidores de configuração e os processos Mongos. Servidores Shard Um servidor de shard consiste em um processo de mongod ou em um conjunto de réplicas de processos mongod. Para a produção, vamos usar um conjunto de réplicas para cada fragmento de dados para segurança e failover automático. Para começar com um teste simples, podemos executar um único processo mongod por shard, como uma configuração de teste não exige failover automatizado. Servidores de configuração Vamos executar um mongod --configsvr para cada servidor de configuração. Se você está apenas testando, pode usar apenas um servidor de configuração. Para a produção, usar três pelo menos. A replicação dos dados para cada configuração do servidor é gerenciada pelo roteador (mongos), pois eles têm um protocolo de replicação síncrona otimizado para três máquinas. Não execute qualquer um dos servidores de configuração com - replSet; pois a replicação entre eles é automática. Mongos Router Execute o processo mongos nos servidores de sua escolha. Especifique o parâmetro configdb para indicar a localização da base de dados de configuração(s). Nota: não use os nomes de DNS, endereços, para o ip. Caso você use, quando mover os servidores de configuração mais tarde, será mais difícil. Note que cada Mongos vai ler o primeiro servidor de configuração na lista fornecida. Se você estiver executando servidores de configuração em mais de um centro de dados, você deve colocar os servidores mais próximos de configuração no início da lista. Exemplo Servidores Shard Primeiramente precisamos criar os serviços de sharding. Esse exemplo é apenas didático e será rodado em apenas um servidor. mkdir /data/db/a /data/db/b mongod --shardsvr --dbpath /data/db/a --port 10000 > /tmp/sharda.log & cat /tmp/sharda.log mongod --shardsvr --dbpath /data/db/b --port 10001 > /tmp/shardb.log & cat /tmp/shardb.log Obs.: O cat serve apenas para verificarmos se o serviço subiu realmente e o & serve para rodar em background. Servidores de Configuração mkdir /data/db/config mongod --configsvr --dbpath /data/db/config --port 20000 > /tmp/ configdb.log & cat /tmp/configdb.log Mongos Router mongos --configdb localhost:20000 > /tmp/mongos.log & cat /tmp/mongos.log Precisamos dizer qual é a nossa database, collection e shard key. db.runCommand( { enablesharding : "test" } ) {"ok" : 1} db.runCommand( { shardcollection : "test.people", key : {name : 1} } ) { "collectionsharded" : "test.people", "ok" : 1 } GridFS GridFS é uma especificação para armazenar grandes arquivos, e ele provê um mecanismo transparente para a divisão dos arquivos em vários documentos. Então, podemos manipular os arquivos como se fossem documentos, ou seja, podemos pesquisar por seus metadados facilmente. Devemos usar o GridFS para arquivos que mudam constantemente; assim, só precisamos atualizá-lo em um nó que os outros serão replicados. Os arquivos são limitados a 16 MB, sendo normalmente divididos em pequenos pacotes chamados chunks de 256 K. Cada chunk é dividido em um documento separado dos outros na coleção de chunks. Já seus metadados são salvos na coleção de arquivos. O interessante de o GridFS quebrar o arquivo em pequenos pedaços é que ele não precisa jogar todo o arquivo na memória RAM como o MongoDB faz com os dados. Em vez disso, ele faz o streaming dos chunks para o nosso usuário. Isso significa que teoricamente você não necessita de mais de 4 MB para trabalhar com os arquivos, independentemente do tamanho deles. GridFS utiliza-se de 2 coleções para o armazenamento dos arquivos: • files - contém os metadados • chunks - contém os arquivos binários e os dados adicionais files O documento da coleção files necessita dos seguintes dados: {<br /> "_id" : <unspecified>, // ID único para este arquivo<br /> "length" : data_number, // tamanho do arquivo em bytes<br /> "chunkSize" : data_number, // tamanho de cada chunks. Padrão é 256k<br /> "uploadDate" : data_date, // data de quando foi armazenado<br /> "md5" : data_string // md5 do arquivo<br />} chunks A estrutura dos documentos dos chunks segue o seguinte padrão: {<br /> "_id" : <unspecified>, // object id of the chunk in the _chunks collection<br /> "files_id" : <unspecified>, // _id of the corresponding files collection entry<br /> "n" : chunk_number, // chunks are numbered in order, starting with 0<br /> "data" : data_binary, // the chunk's payload as a BSON binary type<br / >} Agora vamos ver isso na prática! Abra seu console em uma pasta na qual você possua algum arquivo que queira guardar no GridFS para teste e digite no console: ./mongofiles -d myfiles put minha_musica.mp3 connected to: 127.0.0.1 added file: { _id: ObjectId('4ce9ddcb45d74ecaa7f5a029'), filename: "minha_musica.mp3", chunkSize: 262144, uploadDate: new Date(1290395084166), md5: "7872291d4e67ae8b8bf7aea489ab52c1", length: 1419631 } done! Nesse caso, nós usamos o comando PUT para enviar o arquivo minha_musica.mp3 para a database myfiles na coleção fs.files. Então, vamos listar nossos arquivos agora: $ ./mongofiles -d myfiles list connected to: 127.0.0.1 minha_musica.mp3 1419631 Para pesquisarmos nosso arquivo via console do Mongo, faremos o seguinte: > use myfiles; > db.fs.files.find({}); { "_id" : ObjectId("4ce9ddcb45d74ecaa7f5a029"), "filename" : "03-smbd-menu-screen.mp3", "chunkSize" : 262144, "uploadDate" : "Mon Nov 22 2010 03:04:44 GMT+0000 (UTC)", "md5" : "7872291d4e67ae8b8bf7aea489ab52c1", "length" : 1419631 } Exemplo em PHP Para usarmos o GridFS no PHP, utilizamos a classe MongoGridFS Neste exemplo, usaremos MongoGridFS::storeFile, mas também podemos usar o MongoGridFS::put como havíamos visto anteriormente em linha de comando. <?php // Conecta no Mongo e seta DB e Collection $mongo = new Mongo(); $db = $mongo->myfiles; // GridFS $gridFS = $db->getGridFS(); // Localização da pasta com os arquivos $path = "/tmp/"; $filename = "minha_musica.mp3"; // Olhe os campos metadata e filename $storedfile = $grid->storeFile($path . $filename, array("metadata" => array("filename" => $filename), "filename" => $filename)); // Retorna o ID do arquivo armazenado echo $storedfile; ?> Caso necessite armazenar uma string como arquivo, utilizamos a função storeBytes, como no exemplo abaixo: $storedfile = $grid->storeBytes("Isso é um teste de STRING!", array("metadata" => array("filename" => $filename), "filename" => $filename)); Neste exemplo, vamos ver como recuperar uma imagem do banco e mostrá-la. <?php // Conecta no Mongo e seta DB e Collection $mongo = new Mongo(); $db = $mongo->myfiles; // GridFS $gridFS = $db->getGridFS(); // Procura a imagem $image = $gridFS->findOne("minha_foto.jpg"); // Mostra no navegador header('Content-type: image/jpeg'); echo $image->getBytes(); ?> Agora nos falta apenas deletar o arquivo. Para isso, utilizamos a função remove como para os dados comuns. <?php // Conecta no Mongo e seta DB e Collection $mongo = new Mongo(); $db = $mongo->myfiles; // GridFS $gridFS = $db->getGridFS(); // Procura e deleta o arquivo $removeFile = $gridFS->remove( array("_id" => new MongoId("4ceb167810f1d50a80e1c71c")) ); ?> DBRef O DBRef é uma especificação mais formal para a criação de referências entre os documentos. Sua utilização é mais voltada para ligações entre documentos em que seus valores podem mudar. Como não possuímos JOINs no MongoDB, podemos referenciar ligações entre collections no lado do cliente ou com consultas adicionais. No lado do servidor, podemos referenciar manualmente ou com o DBRef. O DBRefs possui os seguintes campos: • $ref: nome da collection referenciada. • $id: valor do _id do documento referenciado. • $db: opcional e contém o nome da database onde o documento está referenciado. Um documento DBRef pode ser exemplificado assim: { $ref : <value>, $id : <value>, $db : <value> } new DBRef ( 'customers' , ObjectId("4ef4a61a90eec3e3c748263c") ) Porém, o DBRef pode causar maior lentidão nas buscas, pois ele irá fazer a buscas dos documentos atrelados viaDBRef e, caso o documento atrelado seja muito grande, poderá haver uma perda de performance grande. Para isso, utilizamos a referência manual, na qual passamos o vamos do ObjectID diretamente. Vamos a um exemplo utilizando DBRef: db.fruit.save db.fruit.save db.fruit.save db.fruit.save ({"_id" ({"_id" ({"_id" ({"_id" : : : : "1" "2" "3" "4" , , , , "name" "name" "name" "name" : : : : "apple"}); "grape"}); "orange"}); "pineapple"}); db.basket.save ({"_id" : "1", "items" : [ {"quantity" : 5 , item : {"$ref" : "fruit", "$id" : "1"}}, {"quantity" : 10, item : {"$ref" : "fruit", "$id" : "3"}} ]}) db.basket.find() Como podemos ver, o valor do relacionamento está em nosso objeto, porém o objeto relacionado não está. Para resolver isso, nós precisamos utilizar a função fetch() no objeto que contém as referências, como no exemplo: > b = db.basket.findOne() { "_id" : "1", "items" : [ { "quantity" : 5, "item" : { "$ref" : "fruit", "$id" : "1" } }, { "quantity" : 10, "item" : { "$ref" : "fruit", "$id" : "3" } } ] } > b.items [ { "quantity" : 5, "item" : { "$ref" : "fruit", "$id" : "1" } }, { "quantity" : 10, "item" : { "$ref" : "fruit", "$id" : "3" } } ] > { > { b.items[0] "quantity" : 5, "item" : { "$ref" : "fruit", "$id" : "1" } } b.items[0].item.fetch() "_id" : "1", "name" : "apple" } Admin UIs Sistemas para administração visual são uma mão na roda para qualquer banco de dados, como o largamente usado phpmyadmin para o Mysql. Não seria diferente para o MongoDB - existem diversos sistemas desses nas mais variadas linguagens. Uma boa listagem se encontra emhttp://www.mongodb.org/display/DOCS/Admin+UIs Aqui listarei alguns em PHP, que é o nosso foco: PhpMoAdmin - http://www.phpmoadmin.com/ RockMongo - http://code.google.com/p/rock-php/wiki/rock_mongo Frameworks que suportam Vários frameworks e CMS de PHP já possuem um biblioteca para trabalhar com o MongoDB. Entre eles posso listar: CakePHP: • https://github.com/ichikaway/cakephp-mongodb Codeigniter: • https://github.com/kyledye/MongoDB-CodeIgniter-Driver FatFree • http://fatfree.sourceforge.net/page/data-mappers Kohana • https://github.com/Wouterrr/mangodb Lithium • http://lithify.me/docs/lithium/data/source/MongoDb Symfony 2 : • http://code.google.com/p/mongodbloganalyzer/ • http://www.symfony-project.org/plugins/sfStoragePerformancePlugin • https://github.com/brtriver/sfMongoSessionStorage Yii • http://canni.github.com/YiiMongoDbSuite/ Zend Framework • https://github.com/coen-hyde/Shanty-Mongo • https://github.com/stunti/Stunti_Cache_Backend_Mongo • http://framework.zend.com/wiki/display/ZFPROP/Zend_Nosql_Mongo++Valentin+Golev Drupal: • http://drupal.org/project/mongodb Tipos de Dados O MongoDB usa tipos de dados especiais além dos tipos básicos de JSON string, integer, boolean, double, array, null e objeto. Esses tipos incluem date, object id, binary data, regular expression e code. Cada driver implementa esses tipos em linguagem específica. Para conhecer maneiras melhores, consulte a documentação do seu driver. Para verificar qual o tipo do dado no console, basta apesar usar a função typeof. Exemplo: > x { "_id" : ObjectId("4dcd3ebc9278000000005158"), "d" : ISODate("2011-05-13T14:22:46.777Z"), "b" : BinData(0,""), "c" : "suissa", "n" : 27, "e" : [ ], "n2" : NumberLong(33) } > x.d instanceof Date true > x.b instanceof BinData true > typeof x object > typeof x.b object > typeof x.n number > typeof x.n number > typeof x.n2 object > x.n2 instanceof NumberLong true > typeof x.c string Hosts Existem alguns serviços onde você pode hospedar bancos MongoDB: • MongoHQ - https://mongohq.com/ • HostedMongo - http://hostedmongo.com/ • MongoLab - https://mongolab.com/home/ • MongoGrid - http://nodegrid.com/resources/mongogrid/ Classe MongoDB Essa é a classe nativa do MongoDB para PHP. http://php.net/manual/en/ class.mongodb.php Após instanciá-la, você já poderá interagir com o MongoDB. Em nome de databases, é possível usar um grande números de caracteres ASCII, porém eles não podem conter " ", "." ou ser uma string vazia. A palavra "system" também é reservada. Diferentemente das coleções, databases podem conter "$". MongoDB { /* Constants */ const int PROFILING_OFF = 0 ; const int PROFILING_SLOW = 1 ; const int PROFILING_ON = 2 ; /* Fields */ public integer $w = 1 ; public integer $wtimeout = 10000 ; /* Methods */ public array authenticate ( string $username , string $password ) public array command ( array $command [, array $options = array() ] ) public __construct ( Mongo $conn , string $name ) public MongoCollection createCollection ( string $name [, bool $capped = FALSE [, int $size = 0 [, int $max = 0 ]]] ) public array createDBRef ( string $collection , mixed $a ) public array drop ( void ) public array dropCollection ( mixed $coll ) public array execute ( mixed $code [, array $args = array() ] ) public bool forceError ( void ) public MongoCollection __get ( string $name ) public array getDBRef ( array $ref ) public MongoGridFS getGridFS ([ string $prefix = "fs" ] ) public int getProfilingLevel ( void ) public bool getSlaveOkay ( void ) public array lastError ( void ) public array listCollections ( void ) public array prevError ( void ) public array repair ([ bool $preserve_cloned_files = FALSE [, bool $backup_original_files = FALSE ]] ) public array resetError ( void ) public MongoCollection selectCollection ( string $name ) public int setProfilingLevel ( int $level ) public bool setSlaveOkay ([ bool $ok = true ] ) public string __toString ( void ) } Conexão Criar uma conexão entre o PHP e o Mongo é bem simples. Vamos usar o seguinte comando: • $m = new Mongo(); Caso necessite se autenticar para se conectar, use • $m = new Mongo("mongodb://${username}:${password}@localhost"); Após instanciarmos nossa classe Mongo, setamos qual database usaremos. • $db = $m->escola; CRUD Abaixo, veremos um código bem simples para fazermos um CRUD com PHP e MongoDB. Vamos usar um arquivo de configuração apenas para não repetir os dados da conexão. <?php //mongo_config.php $conn = new Mongo(); $db = $conn->test; $collection = $db->people; ?> Create Criamos nosso index.php com o nosso formulário para inserirmos os dados. <?php require_once('mongo_config.php'); ?> <form action="cria_usuarios.php" method="post"> <p> <label for="name">Nome:</label> <input type="text" name="nome"> </p> <p> <label for="age">Idade:</label> <input type="text" name="idade"> </p> <p> <label for="likes">Likes:</label> <input type="text" name="likes"> </p> <p> <input type="submit" value="Save"> </p> </form> Depois criamos o create_users.php, que será a página que receberá nosso POST do form. <?php require_once('mongo_config.php'); //create_users.php function check_empty($ar){ $empty = 0; if(is_array($ar)){ foreach($ar as $v){ if(empty($v)){ $empty = 1; } } } return $empty; } if(!empty($_POST)){ $people = $collection->find(); $people_count = $people->count(); $empty = check_empty($_POST); if($empty != 1){ $id = $people_count + 1; $name = $_POST['name']; $age = $_POST['age']; $likes = $_POST['likes']; $person = array('id'=>$id, 'name'=>$name, 'age'=>$age, 'likes'=>$likes); $collection->insert($person); echo $name . ' is registered!'; }else{ echo 'Please fill out all the fields!'; } } ?> Read Agora criamos outra página chamada listagem.php e colocamos o código abaixo: <?php require_once('mongo_config.php'); $people = $collection->find(); $people_count = $people->count(); echo $people_count . ' pessoas achadas<br/>'; if($people_count > 0){ ?> <table border="1"> <thead> <tr> <th>ID</th> <th>Nome</th> <th>Idade</th> <th>Likes</th> </tr> </thead> <tbody> <?php foreach($people as $v){ ?> <tr> <td><a href="update_user.php?nome=<?php echo $v['nome']; ? >"><?php echo $v['id']; ?></a></td> <td><?php echo $v['nome']; ?></td> <td><?php echo $v['idade']; ?></td> <td><?php echo $v['likes']; ?></td> </tr> <?php } ?> </tbody> </table> <?php } ?> Update Precisamos listar todos os usuários novamente, porém, desta vez, não vamos apenas mostrá-los em uma tabela, vamos dar um link para cada usuário, para que eles sejam levados para o form de atualização. <a href="atualiza_usuario.php?name=<?php echo $v['name']; ?>"> Depois, criamos a página atualiza_usuario.php e colocamos o seguinte código: <?php require_once('mongo_config.php'); // Aqui recebemos o POST do FORM atualizando os dados if(!empty($_POST)){ $empty = check_empty($_POST); if($empty != 1){ $nome = $_POST['nome']; $idade = $_POST['idade']; $likes = $_POST['likes']; $query = array('nome'=>$nome); $person = $collection->findOne($query); $person['idade'] = $idade; $person['likes'] = $likes; $collection->save($person); echo 'Update Successful!'; } } // Aqui recebemos o nome da pessoa que estamos querendo atualizar if(!empty($_GET['nome'])){ $nome = $_GET['nome']; $query = array('nome'=>$nome); $person = $collection->findOne($query); $nome = $person['nome']; $idade = $person['idade']; $likes = $person['likes']; ?> <form action="update_user.php" method="post"> <p> <label for="name">Nome:</label> <input type="text" name="nome" value="<?php echo $nome; ?>" readonly> </p> <p> <label for="age">Idade:</label> <input type="text" name="idade" value="<?php echo $idade; ?>"> </p> <p> <label for="likes">Likes:</label> <input type="text" name="likes" value="<?php echo $likes; ?>"> </p> <p> <input type="submit" value="Update"> </p> </form> <?php } ?> Delete Agora o código para deletar vai ficar por sua conta. Lembre-se do findOne e do remove. Exercício Depois de termos um CRUD funcional com PHP, vamos fazer um pequeno exercício, transformando nosso exemplo anterior em um CRUD orientado a objetos. Nesse exercício, você precisará criar uma classe de Conexão com o MongoDB, uma classe de Pessoa com suas funções, uma página como Controller da ações e suas respectivas Views. Podem entrar em contato comigo para tirar suas dúvidas e validar o exercício. Sistema de Tarefas Neste módulo, criaremos um sistema simples de tarefas para entendermos o conceito por trás do MongoDB. Não quis me focar muito na separação dos arquivos, e sim na funcionalidade. Creio que a partir dessa base você já estará apto para criar sistemas simples que se encaixem bem com o MongoDB.