Tag Archives: mongodb

Utiliser Mongodb pour collecter des tweets avec le sample du firehose

Dans le cadre de tests à mon travail, j’ai eu l’occasion de gouter à l’API de streaming twitter (enfin qu’à sa version de démonstration, samplée à uniquement 5% du traffic total). Le but de la maneouvre ici est de récupérer et de stocker le flux et d’opérer des requêtes ponctuelles sur la base (récupération de tweets d’un user donné, requêtes par localités, etc.).

Je vous livre ici les scripts

<?php

$sampleUrl = 'http://stream.twitter.com/1/statuses/sample.json';

$user = '** votre login twitter **';
$pass = '** votre password twitter **';

// Connexion sur la base Mongo
$mongo_cnx = new Mongo();
$db = $mongo_cnx->twitter;

$fp = fopen("http://" . $user . ":" . $pass . "@stream.twitter.com/1/statuses/sample.json", "r");

while($data = fgets($fp))
{
    // Récupération du tweet complet fourni par Twitter
    $tweet = json_decode(trim($data));
   
    // Si le tweet n'a pas d'auteur, on le drop. C'est le cas sur les effacements de tweets.
    if( ! isset($tweet->user) )
        continue;
   
    // On découpe le tweet et son user (qui seront tous deux stockés dans les deux tables suivantes):
    $tweet_user = get_object_vars($tweet->user);
    $tweet_mesg = get_object_vars($tweet);
    unset($tweet_mesg['user']);
   
    $tweet_mesg['user_id'] = $tweet_user['id'];
   
    // Stockage du tweet et de son auteur:
    $db->user->insert($tweet_user);
    $db->tweet->insert($tweet_mesg);
   
    echo "Tweet " . $tweet_mesg['id'] . " done.\n";
}

fclose($fp);

Et on le lance:

$ php fetch_twitter.php
...
Tweet 16774425803 done.
Tweet 16774426103 done.
Tweet 16774426201 done.
Tweet 16774426202 done.
Tweet 16774426200 done.
...

Pendant le runtime, on peut regarder évoluer la base:

$ ./bin/mongo
MongoDB shell version: 1.4.3
url: test
connecting to: test
type "help" for help
> use twitter
switched to db twitter
> db.tweet.find().count()
457811
> db.user.find().count()
457811
...

Les applications sont multiples, en voici une simple qui prend les dix derniers tweets, et remonte leurs auteurs:

<?php

$mongo_cnx = new Mongo();
$db = $mongo_cnx->twitter;

$count = $db->tweet->count();

echo "There is $count tweets.\n";

$cur = $db->tweet->find()->skip( $count - 10 );

function getUser($db, $user_id)
{
    $user = $db->user->findOne( array( 'id' => $user_id ) );
    return $user;
}

foreach($cur as $tweet)
{
    $user = getUser($db, $tweet['user_id']);
    echo "From " . $user['name'] . ": " . $tweet['text'] . "\n";
}

En voici une seconde rapide qui va remonter les tweets des utilisateurs ayant définie une langue « fr »:

<?php

$mongo_cnx = new Mongo();
$db = $mongo_cnx->twitter;

$cur = $db->user->find(array("lang" => "fr"));

foreach($cur as $user)
{
    $tweet_cur = $db->tweet->find(array("user_id" => $user['id']));
    foreach($tweet_cur as $tweet)
    {
        echo $tweet['text'] . "\n";
    }  
}

Installer l’extension PHP de Mongodb et son utilisation

Pour utiliser une base MongoDB avec PHP, il faut installer l’extension Mongo (http://www.php.net/manual/en/mongo.installation.php). Comme c’est assez simple, ayant déjà tous les outils pour compiler une extension PHP, j’ai du coup préféré la voie de la compilation:

On commence par se créer un répertoire, et on choppe les sources sur le dépôt officiel git:

$ cd /tmp/mongodb/php
$ git clone http://github.com/mongodb/mongo-php-driver.git
Initialized empty Git repository in /tmp/mongodb/php/mongo-php-driver/.git/
remote: Counting objects: 4106, done.
remote: Compressing objects: 100% (1338/1338), done.
remote: Total 4106 (delta 3100), reused 3670 (delta 2742)
Receiving objects: 100% (4106/4106), 1.02 MiB | 269 KiB/s, done.
Resolving deltas: 100% (3100/3100), done.

La compilation et l’installation du module ne diffère pas par rapport à un autre module PHP:

$ cd mongo-php-driver
$ phpize
Configuring for:
PHP Api Version:         20041225
Zend Module Api No:      20060613
Zend Extension Api No:   220060519

$ ./configure --enable-mongo
[...]
configure: creating ./config.status
config.status: creating config.h
config.status: executing libtool commands

$ make
[...]

Build complete.
Don't forget to run 'make test'.

Et finalement, en tant que root:

$ make install
Installing shared extensions:     /usr/lib/php/modules/

$ cat > /etc/php.d/mongo.ini << EOF
> extension=mongo.so
> EOF

On pourra vérifier la présence de l’extension:

$ php -i |grep -i mongo
/etc/php.d/mongo.ini,
MongoDB Support => enabled
[...]

Il ne reste plus qu’à tester du code php. La documentation est ici http://www.php.net/manual/en/mongo.manual.php.
Dans le script suivant, je prends pour exemple la base que j’ai créée dans un précédent article, et j’en adapte mes actions à un petit script PHP qui s’explique – presque – tout seul:

<?php

// Connection sur le serveur
$mongo_cnx = new Mongo();

// On utilise la base de données 'testdb' avec laquelle
// on a fait des tests précédemment:
$db = $mongo_cnx->testdb;

// On 'scanne' la collection 'people' dans laquelle
// on va retrouver nos enregistrements:
foreach($db->people->find() as $person)
{
    echo $person['name'] . ' - ' . $person['age'] . "\n";
}
// xin - 24
// patrick - 28
// patrick - 27

// On peut rajouter de nouvelles données:
$db->people->insert( array('name' => 'red', 'age' => 12) );

// On peut également faire des requetes plus précises comme dans
// le 'pseudo shell' mongo:
$red = $db->people->findOne( array('age' => 12) );
// $red sera:
// array(3) {
//   ["_id"]=>
//   object(MongoId)#7 (0) {
//   }
//   ["name"]=>
//   string(3) "red"
//   ["age"]=>
//   int(12)
// }

// Mise à jour de notre objet, en spécifiant un nouveau champs:
$red['city'] = 'Paris';

// Une mise à jour prend en premier argument le critère descriptif
// des objets à updater et l'objet à utiliser comme modification.
$db->people->update(array('name' => 'red', 'age' => 12), $red);
echo "There is now " . $db->people->count() . " person(s) in database.\n";

// On vérifie que la modification a été effectuée:
var_dump( $db->people->findOne( array('name' => 'red') ) );
// array(4) {
// ...
//   ["city"]=>
//   string(5) "Paris"

// Et les supprimer:
$r = $db->people->remove($red);
if( $r ) echo "Record deleted !\n";

$red = $db->people->findOne( array('name' => 'red') );
var_dump($red);
// Renvoie NULL.

Mongodb: Installation et premiers pas.

Mongodb est un soft opensource de DB non relationel (Nosql), orienté document sans schéma écrit en C++. L’avantage des systèmes Nosql sont les hautes performances et les contraintes moindre grâce à la non présence de schéma de données.

J’ai testé il y a quelques mois HBase, mais même si les performances étaient relativement bonnes pour ce que je voulais faire (je le décrirai ici un jour), je voulais me faire une idée sur les autres solutions, et plus particulièrement Mongodb puis peut être plus tard Tokyo Cabinet (ou son successeur Kyoto Cabinet).

Je décris dans cet article comment installer et tester rapidement un serveur mongodb. Dans des articles suivants, je testerai notamment le drivers PHP et comment faire des requêtes de lecture et écriture en base.

Télécharger mongdb:

$ mkdir /tmp/mongodb ; cd !-1:1
$ wget http://downloads.mongodb.org/linux/mongodb-linux-i686-1.4.3.tgz
$ tar xvfz mongodb-linux-i686-1.4.3.tgz

Lancer le serveur:

$ mkdir /tmp/mongodb/db/
$ cd mongodb-linux-i686-1.4.3
$ ./bin/mongod --dbpath /tmp/mongodb/db/
Fri Jun  4 11:07:26 Mongo DB : starting : pid = 28548 port = 27017 dbpath = /tmp/mongodb/db/ master = 0 slave = 0  32-bit
Fri Jun  4 11:07:26 db version v1.4.3, pdfile version 4.5
Fri Jun  4 11:07:26 git version: 47ffbdfd53f46edeb6ff54bbb734783db7abc8ca
[...]

Dans un autre terminal, lancement d’un client et test de quelques requêtes:

On utilise la db « testdb »:

> use testdb
switched to db testdb

Dans laquelle on enregistre quelques valeurs:

> person = { 'name': 'xin', 'age': 24 }
{ "name" : "xin", "age" : 24 }
> db.people.save( person )
> person = { 'name': 'patrick', 'age': 28 }
{ "name" : "patrick", "age" : 28 }
> db.people.save( person )

On vérifie le contenu de la base:

> db.people.find()
{ "_id" : ObjectId("4c08c38bb91ea16336bdddf2"), "name" : "xin", "age" : 24 }
{ "_id" : ObjectId("4c08c394b91ea16336bdddf3"), "name" : "patrick", "age" : 28 }

On voit que mongodb rajoute pour chaque enregistrement un id interne.

On peut limiter le nombre de résultat par requête:

> db.people.find().limit(1)
{ "_id" : ObjectId("4c08c38bb91ea16336bdddf2"), "name" : "xin", "age" : 24 }

Ou encore affiner le résultat en cherchant des enregistrements par valeurs spécifiques:

> db.people.find({ 'age' : 24 })
{ "_id" : ObjectId("4c08c38bb91ea16336bdddf2"), "name" : "xin", "age" : 24 }

On pourra enregistrer le résultat des requêtes dans des variables:

> var my_result = db.people.findOne( { 'age': 28 } )      
> print(tojson(my_result))
{
        "_id" : ObjectId("4c08c394b91ea16336bdddf3"),
        "name" : "patrick",
        "age" : 27
}

Plus d’information sur la page de tutoriel sur mongodb.org.