Skip to content

Instantly share code, notes, and snippets.

@fredericorecsky
Created March 16, 2012 22:41
Show Gist options
  • Save fredericorecsky/2053335 to your computer and use it in GitHub Desktop.
Save fredericorecsky/2053335 to your computer and use it in GitHub Desktop.
perl e mongoDB

Perl e MongoDB

Introdução

Há algum tempo atrás começou uma onda de NOSQL e eu a principio só me mantive observando a distancia. A questão é que como toda coisa nova acaba sendo sobreutilizada ou utilizada em funções erradas.

No caso existem na internet diversos textos sobre o Mongo, usos e etc. Nesse artigo vou abordar um uso bem simples mas que por conta das caracteristicas do mongodb o tornaram quase que uma luva.

Essas caracteristicas são schema free e documented oriented. No caso estou desenvolvendo um leitor de rss/atom/xml e que eu preciso escrever pequenos "pontos de dados" e que roda em cluster. Também ele tem diversos crawlers internos dos quais cada um tem sua configuração.

É possivel fazer utilizando mysql ou postgres ou qualquer outro, mas a simplicidade de se erguer um servidor mongo sem esquema definido e que possibilita salvar documentos json direto facilita muito o desenvolvimento. Especialmente para empresas que tem seus negocios apenas baseado em aplicações internet que não tem de fato a arquitetura final definida.

Requisitos

Claro que Perl, uma instancia de MongoDB e o modulo do MongoDB do cpan.

*http://www.mongodb.org/.

*http://search.cpan.org/~kristina/MongoDB-0.45/lib/MongoDB.pm.

Para começar estou assumindo que o programa perl está na mesma maquina em que o mongo está rodando, mas é trivial configurar para um servidor diferente.

Conexão

use strict;
use warnings;

use MongoDB;

my $connection  = MongoDB::Connection->new( host => 'localhost', port => 27017);
my $database    = $connection->get_database('sppm');
my $collection  = $database->get_collection('equinocio');
my $data        = $collection->find_one({ type => "artigo" });

Além do header classico, temos a declaração do modulo do MongoDB e as strings de conexão e de escolha de database e collection. Collection é um nome bonito para o que seria uma tabela num banco de dados classico.

Note que se o database não existir no MongoDB será autovivificado, assim como a collection. Se você rodar esse exemplo $data estara vazio por motivos obvios :).

Vamos assumir que esse script roda eventualmente pegando a tabela de artigos da sppm e avisa se há novos. A Tabela de artigos tem esse schema:

artigo, data, link

Obvio que eu não gostaria de varrer todos os registros toda vez, assim como que por algum motivo valido eu não quero ou não posso alterar o banco de dados. Então eu gostaria que esse script, salvasse a data em que ele leu da ultima vez a tabela para que na proxima continuasse de onde parou.

Então depois de varrer a tabela eu gostaria de salvar somente o ultimo timestamp em que o script o fez.

my $timestamp_id = $collection->insert({
                            type          => 'last_verified',
                            last_verified => '1999/12/31 23:59:59'
                   });

Isso insere o valor "last_verified" na collection equinocio e retorna uma id, no caso, "4f6367450be985cd5c000000". Note que ao inserir uma nova chave é gerado um novo id. Esse id é o id do conjunto de chave valor. E voce pode ter duas chaves valores iguais. Note que criamos um tipo, indicado por type. Isso é apenas para diferenciar eventuais outras entradas nessa collection.

Se executar duas vezes e consultar no console do mongo db:

> db.equinocio.find()
{
  "_id" : ObjectId("4f63a04a367cf6c772000000"),
  "last_verified" : "1999/12/31 23:59:59",
  "type" : "last_verified"
}
{
  "_id" : ObjectId("4f63a05b097b8f1b73000000"),
  "last_verified" : "1999/12/31 23:59:59",
  "type" : "last_verified"
}

Agora temos duas entradas para "last_verified".

Collections

No nosso caso queremos apenas salvar a data que nós varremos o banco de dados pela ultima vez. Então vamos apagar as entradas e vamos criar só uma. Quando o script for executado ele tentara primeiro recuperar last_verified e depois escrevera o resultado.

A classe que manipula Collections no Mongo é MongoDB::Collection:

*http://search.cpan.org/~kristina/MongoDB-0.45/lib/MongoDB/Collection.pm

Vamos apagar todos os type "last_verified" para começar de novo, primeiro vamos achar todos objetos "last_verified".

my $data = $collection->find({ type => 'last_verified'  } );

$data é um objeto MongoDB::Cursor, um iterador para varios items numa lista de resultados. Como eu executei varias vezes temos uma coleção de timestamps repetidos...

jeferson@ps80344:~/Reader/bin$ perl mongo.pl
$VAR1 = {
       '_id' => bless( {
                         'value' => '4f6367450be985cd5c000000'
                       }, 'MongoDB::OID' ),
       'last_verified' => '1999/12/31 23:59:59'
     };
$VAR1 = {
       '_id' => bless( {
                         'value' => '4f6368b7279b784b6b000000'
                       }, 'MongoDB::OID' ),
       'last_verified' => '1999/12/31 23:59:59'
     };
$VAR1 = {
       '_id' => bless( {
                         'value' => '4f636a8cd8947c5c7e000000'
                       }, 'MongoDB::OID' ),
       'last_verified' => '1999/12/31 23:59:59'
     };

Vamos apaga-los e manter somente um. A classe Cursor permite que se apague (drop) todo o collection de uma vez:

$collection->drop;

Mas queremos apagar todos menos um. De quebra aprendemos sobre o OID.

OID

Cada documento armazenado contem um id unico, Object Id, ( vulgo OID ). Ele é um hash key para cada set de key values e é unico. Para apagar vamos iterar sobre o resultado ( Cursor ) e apagar todos menos um. Para apagar devemos passar uma query para o metodo remove. Isso significa que se para apagar sub-sets de dados semelhantes temos que recolher os ids e apaga-los individualmente.

Então temos:

my $data = $collection->find();

my @all_copies =  $data->all;

pop @all_copies;

for my $object ( @all_copies ){

    $collection->remove( { _id => $object->{ _id } } ) ;

}

Primeiro recuperamos todos os registros com um find() sem parametros. Salvamos todos os OID num array e removemos um com pop. Esse removido sera salvo. Depois iteramos os objetos e os removemos.

$object->{ _id } # Mongo::OID dentro do resultado do Cursor.

O cursor retorna o OID mais um hash com os keys values. Não é muito sabio usar '_id' como chave quando estiver utilizando-o.

Agora que temos apenas um documento com a ultima data, podemos utilizar para os devidos fins.

use Data::Dumper;
use DateTime;
use MongoDB;

my $connection = MongoDB::Connection->new(host => 'localhost', port => 27017 );
my $database   = $connection->get_database('sppm');
my $collection = $database->get_collection('equinocio');

my $timestamp_id = $collection->find_one( { type => 'last_verified' } );

if ( $timestamp_id ) {
   print $timestamp_id->{ last_verified } , " \n" ;
} else {
   $timestamp_id =  $collection->insert({ type => 'last_verified' ,  last_verified => '1999/12/31 23:59:59' });
}

O codigo vai ler o timestamp no MongoDB, e caso exista ele utilizara, ou caso contrario ele criara um hardcoded para o bug do milenio. Esse mecanismo é similar a outro muito comum, cache. Você procura se existe no cache e caso não encontre você partira para a proxima camada. No caso um select max(timestamp) na tabela de artigos.

No caso o uso mais comum para mim é criar documentos ( renderizações de paginas ) em json e salva-las no MongoDB. Mantenho uma lista indexada dos documentos e faço o retorno facil. Em geral para gerar esses documentos do banco de dados RDBMS são feitas diversas queries, e mesmo quentes na memoria ainda iriam gerar o documento final. Tenho utilizado o mongo como cache desses documentos com sucesso. Obvio que depende de varios fatores e isso não é uma bala de prata mas é meu caso de uso atual.

Outro uso é simplesmente salvar documentos, como os resultados de um web crawler para o banco de dados. Cada pagina tem seu tamanho e suas caracteristicas e ele funciona bem para salvar esse tipo de dados.

AUTHOR

Frederico Recsky <cartas@frederico.me>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment