NOSQL O que é? Por que usar?

Propaganda
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.
Download