web-dev-qa-db-fra.com

Comment écouter les modifications apportées à une collection MongoDB?

Je crée une sorte de système de file d'attente de tâches en arrière-plan avec MongoDB comme magasin de données. Comment puis-je "écouter" les insertions dans une collection MongoDB avant que des travailleurs ne se reproduisent pour traiter le travail? Dois-je interroger toutes les quelques secondes pour voir si des modifications ont été apportées depuis la dernière fois ou mon script peut-il attendre que des insertions aient lieu? Il s'agit d'un projet PHP sur lequel je travaille, mais n'hésitez pas à répondre en Ruby ou en langage agnostique.

184
Andrew

MongoDB a ce qu'on appelle capped collections et tailable cursors qui permet à MongoDB d'envoyer des données aux écouteurs.

Un _capped collection_ est essentiellement une collection de taille fixe qui autorise uniquement les insertions. Voici à quoi cela ressemblerait pour en créer un:

_db.createCollection("messages", { capped: true, size: 100000000 })
_

Curseurs MongoDB disponibles ( message original de Jonathan H. Wage )

Ruby

_coll = db.collection('my_collection')
cursor = Mongo::Cursor.new(coll, :tailable => true)
loop do
  if doc = cursor.next_document
    puts doc
  else
    sleep 1
  end
end
_

PHP

_$mongo = new Mongo();
$db = $mongo->selectDB('my_db')
$coll = $db->selectCollection('my_collection');
$cursor = $coll->find()->tailable(true);
while (true) {
    if ($cursor->hasNext()) {
        $doc = $cursor->getNext();
        print_r($doc);
    } else {
        sleep(1);
    }
}
_

Python (par Robert Stewart)

_from pymongo import Connection
import time

db = Connection().my_db
coll = db.my_collection
cursor = coll.find(tailable=True)
while cursor.alive:
    try:
        doc = cursor.next()
        print doc
    except StopIteration:
        time.sleep(1)
_

Perl (by Max )

_use 5.010;

use strict;
use warnings;
use MongoDB;

my $db = MongoDB::Connection->new;
my $coll = $db->my_db->my_collection;
my $cursor = $coll->find->tailable(1);
for (;;)
{
    if (defined(my $doc = $cursor->next))
    {
        say $doc;
    }
    else
    {
        sleep 1;
    }
}
_

Ressources additionnelles:

Tutoriel Ruby/Node.js qui vous guide dans la création d’une application qui écoute les insertions dans une collection limitée par MongoDB.

n article sur les curseurs disponibles plus en détail.

exemples PHP, Ruby, Python et Perl d'utilisation de curseurs disponibles.

98
Andrew

Ce que vous pensez ressemble beaucoup à des déclencheurs. MongoDB ne prend pas en charge les déclencheurs, mais certaines personnes ont "fait leur propre" en utilisant certaines astuces. La clé ici est le oplog.

Lorsque vous exécutez MongoDB dans un jeu de réplicas, toutes les actions de MongoDB sont consignées dans un journal des opérations (appelé oplog). L'oplog est fondamentalement juste une liste récapitulative des modifications apportées aux données. Les ensembles de répliques fonctionnent en écoutant les modifications apportées à cet oplog, puis en les appliquant localement.

Cela vous semble-t-il familier?

Je ne peux pas détailler l’ensemble du processus ici, c’est plusieurs pages de documentation, mais les outils dont vous avez besoin sont disponibles.

Quelques premiers écrits sur oplog - Brève description - Mise en page de la collection local (qui contient oplog)

Vous voudrez également tirer parti de curseurs disponibles . Celles-ci vous fourniront un moyen d'écouter les modifications au lieu de les interroger. Notez que la réplication utilise des curseurs disponibles, il s'agit donc d'une fonctionnalité prise en charge.

108
Gates VP

Depuis MongoDB 3.6, il y aura une nouvelle API de notification appelée Change Streams que vous pourrez utiliser pour cela. Voir cet article de blog pour un exemple . Exemple de celui-ci:

cursor = client.my_db.my_collection.changes([
    {'$match': {
        'operationType': {'$in': ['insert', 'replace']}
    }},
    {'$match': {
        'newDocument.n': {'$gte': 1}
    }}
])

# Loops forever.
for change in cursor:
    print(change['newDocument'])
39
Mitar

Découvrez ceci: Changer les flux

10 janvier 2018 - Version 3.6

* EDIT: j'ai écrit un article sur la façon de procéder https://medium.com/riow/mongodb-data-collection-change-85b63d96ff76

https://docs.mongodb.com/v3.6/changeStreams/


C'est nouveau dans mongodb 3.6 https://docs.mongodb.com/manual/release-notes/3.6/ 2018/01/10

$ mongod --version
db version v3.6.2

Afin d'utiliser changeStreams , la base de données doit être un jeu de réplication

Informations complémentaires sur les jeux de réplication: https://docs.mongodb.com/manual/replication/

Votre base de données sera un " autonome " par défaut.

Comment convertir un fichier autonome en jeu de réplicas: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/


L'exemple suivant est une application pratique pour vous en servir.
* Spécifiquement pour le nœud.

/* file.js */
'use strict'


module.exports = function (
    app,
    io,
    User // Collection Name
) {
    // SET WATCH ON COLLECTION 
    const changeStream = User.watch();  

    // Socket Connection  
    io.on('connection', function (socket) {
        console.log('Connection!');

        // USERS - Change
        changeStream.on('change', function(change) {
            console.log('COLLECTION CHANGED');

            User.find({}, (err, data) => {
                if (err) throw err;

                if (data) {
                    // RESEND ALL USERS
                    socket.emit('users', data);
                }
            });
        });
    });
};
/* END - file.js */

Liens utiles:
https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set
https://docs.mongodb.com/manual/tutorial/change-streams-example

https://docs.mongodb.com/v3.6/tutorial/change-streams-example
http://plusnconsulting.com/post/MongoDB-Change-Streams

30
Rio Weber

MongoDB version 3.6 inclut désormais les flux de modifications, qui sont essentiellement des API superposées à OpLog, permettant des cas d'utilisation analogues à des déclencheurs/notifications.

Voici un lien vers un exemple Java: http://mongodb.github.io/mongo-Java-driver/3.6/driver/tutorials/change-streams/

Un exemple de NodeJS pourrait ressembler à quelque chose comme:

 var MongoClient = require('mongodb').MongoClient;
    MongoClient.connect("mongodb://localhost:22000/MyStore?readConcern=majority")
     .then(function(client){
       let db = client.db('MyStore')

       let change_streams = db.collection('products').watch()
          change_streams.on('change', function(change){
            console.log(JSON.stringify(change));
          });
      });
19
Robert Walters

Vous pouvez également utiliser la méthode Mongo FindAndUpdate standard et déclencher un événement EventEmitter (dans le nœud) lors du rappel lors de l'exécution du rappel.

Toute autre partie de l'application ou de l'architecture écoutant cet événement sera avisée de la mise à jour et de toutes les données pertinentes qui y seront envoyées. C'est un moyen très simple d'obtenir des notifications de Mongo.

3
Alex

Plusieurs de ces réponses ne vous donneront que de nouveaux enregistrements et non des mises à jour et/ou sont extrêmement inefficaces

Le seul moyen fiable et performant de le faire est de créer un curseur disponible sur la collection locale db: oplog.rs pour obtenir TOUTES les modifications apportées à MongoDB et en faire ce que vous voulez. (MongoDB le fait même plus ou moins en interne pour supporter la réplication!)

Explication de ce que contient le journal des opérations: https://www.compose.com/articles/the-mongodb-oplog-and-node-js/

Exemple de bibliothèque Node.js fournissant une API autour de ce qui peut être fait avec le journal oplog: https://github.com/cayasso/mongo-oplog

2
John Culviner

Il existe un exemple de travail Java qui peut être trouvé ici .

 MongoClient mongoClient = new MongoClient();
    DBCollection coll = mongoClient.getDatabase("local").getCollection("oplog.rs");

    DBCursor cur = coll.find().sort(BasicDBObjectBuilder.start("$natural", 1).get())
            .addOption(Bytes.QUERYOPTION_TAILABLE | Bytes.QUERYOPTION_AWAITDATA);

    System.out.println("== open cursor ==");

    Runnable task = () -> {
        System.out.println("\tWaiting for events");
        while (cur.hasNext()) {
            DBObject obj = cur.next();
            System.out.println( obj );

        }
    };
    new Thread(task).start();

La clé est OPTIONS DE RECHERCHE donnée ici.

Vous pouvez également modifier la requête de recherche si vous n'avez pas besoin de charger toutes les données à chaque fois.

BasicDBObject query= new BasicDBObject();
query.put("ts", new BasicDBObject("$gt", new BsonTimestamp(1471952088, 1))); //timestamp is within some range
query.put("op", "i"); //Only insert operation

DBCursor cur = coll.find(query).sort(BasicDBObjectBuilder.start("$natural", 1).get())
.addOption(Bytes.QUERYOPTION_TAILABLE | Bytes.QUERYOPTION_AWAITDATA);
1
Maleen Abewardana

En fait, au lieu de regarder la sortie, pourquoi ne pas être averti quand quelque chose de nouveau est inséré en utilisant du middleware fourni par schéma de mangouste

Vous pouvez intercepter l'événement d'insertion d'un nouveau document et faire quelque chose après cette insertion

1
Duong Nguyen

Il existe un ensemble impressionnant de services appelés Point MongoDB . Regardez dans fonctions de point/déclencheurs . Notez qu’il s’agit d’un service AWS payant basé sur un nuage. Dans votre cas, sur une insertion, vous pouvez appeler une fonction personnalisée écrite en javascript.

enter image description here

0
Manish Jain