MongoDBコレクションへの変更をリッスンする方法は?


200

MongoDBをデータストアとして使用して、一種のバックグラウンドジョブキューシステムを作成しています。ジョブを処理するワーカーを生成する前に、MongoDBコレクションへの挿入を「リッスン」するにはどうすればよいですか?前回からの変更があるかどうかを確認するために数秒ごとにポーリングする必要がありますか、それともスクリプトが挿入の発生を待機する方法はありますか?これは私が取り組んでいるPHPプロジェクトですが、Rubyまたは言語にとらわれずに自由に回答してください。


1
Change Streamsは、シナリオに対応するためにMongoDB 3.6に追加されました。 docs.mongodb.com/manual/changeStreams また、MongoDB Atlasを使用している場合は、挿入/更新/削除などに応じて関数を実行できるStitch Triggersを利用できます。 docs.mongodb.com/stitch/triggers/overview oplogを解析する必要はもうありません。
ロバートウォルターズ

回答:


111

あなたが考えていることは、トリガーのように聞こえます。MongoDBはトリガーをサポートしていませんが、一部のトリックを使用して「自分でロール」した人もいます。ここでの鍵はoplogです。

レプリカセットでMongoDBを実行すると、すべてのMongoDBアクションが操作ログ(oplogと呼ばれる)に記録されます。oplogは基本的に、データに対して行われた変更の単なる実行リストです。レプリカセットは、このoplogの変更をリッスンし、変更をローカルに適用することによって機能します。

これは聞き覚えがありますか?

ここではプロセス全体を詳しく説明することはできません。数ページのドキュメントですが、必要なツールを利用できます。

最初にoplogに関するいくつかの説明 - 簡単な説明 - コレクションのレイアウトlocal(oplogを含む)

また、テールカーソルを活用することもできます。これらは、変更をポーリングする代わりに、変更をリッスンする方法を提供します。レプリケーションではテール可能なカーソルが使用されるため、これはサポートされている機能です。


1
うーん...私が考えていたものとは正確には違います。この時点で実行しているインスタンスは1つだけです(スレーブはありません)。おそらくもっと基本的な解決策でしょうか?
Andrew

17
--replSetオプションを使用してサーバーを起動すると、サーバーが作成/設定されますoplog。セカンダリなしでも。これは間違いなく、DBの変更を「聞く」唯一の方法です。
Gates VP

2
これは、ローカルでDBへの変更をログに記録するためにoplogを設定する方法を説明したものです。loosexaml.wordpress.com
2012/09/03

やったー!それが私が本当に望んでいることです。そして、npmで「mongo-oplog」という名前のライブラリを見つけました。とても幸せです
pjincz

この回答を書いている時点では、トリガーは利用できないかもしれませんが、ここに上陸したすべての人が利用できる可能性があることに同意します。現在利用可能なオプションがあります。MongoDBStitchdocs.mongodb.com/stitch/#stitch)とStitchトリガー(docs。 mongodb.com/stitch/triggers)..
whoami

102

MongoDBにはいわゆる機能がcapped collectionsありtailable cursors、MongoDBがデータをリスナーにプッシュできるようにします。

A capped collectionは本質的には固定サイズのコレクションであり、挿入のみを許可します。これを作成すると次のようになります。

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

MongoDB Tailableカーソル(Jonathan H. Wageによる元の投稿

ルビー

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);
    }
}

PythonRobert 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)

PerlMaxによる

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;
    }
}

追加のリソース:

Ruby / Node.jsチュートリアルでは、MongoDBのキャップされたコレクションへの挿入をリッスンするアプリケーションの作成について説明しています。

テーラブルカーソルについて詳しく説明している記事。

PHP、Ruby、Python、Perlの例で、テイル可能カーソルを使用しています。


70
寝る?本当に?量産コードについては?どのようにポーリングしないのですか?
rbp

2
@rbpハハ、私はそれがプロダクションコードであるとは決して言っていませんが、あなたが正しいです、ちょっと寝るのは良い習慣ではありません。別の場所からその例を得たことは間違いありません。それをリファクタリングする方法はわかりませんが。
Andrew

14
@kroeは、これらの無関係な詳細が、なぜそれが悪いのか理解していない新しいプログラマーによって本番コードに組み込まれるためです。
ナマズ

3
私はあなたの要点を理解しましたが、一部の新しいプログラマが「スリープ1」をプロダクションに追加することを期待することはほとんど不快です!私は、私は驚かないだろう、意味...しかし、誰かのプット場合、これは生産で、少なくとも永遠にハードな方法とを学びます...笑
kroe

19
本番環境でtime.sleep(1)を実行することの何が問題になっていますか?
Al Johri 16

44

MongoDB 3.6以降では、これに使用できるChange Streamsと呼ばれる新しい通知APIがあります。例については、このブログ投稿を参照してください。それからの例:

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'])

4
どうして?詳しく説明できますか?これは今の標準的な方法ですか?
Mitar 2017

1
どうやって?ポーリングを使用しないでください-whileループなどの代わりにイベントアプローチが必要です。–
Alexander Mills

3
ここで投票はどこにありますか?
Mitar 2018年

彼/彼女は最後のループについて言及していると思います。しかし、PyMongoはそれのみをサポートしていると思います。モーターには非同期/イベントリスナースタイルの実装がある場合があります。
シェーン・スー

41

これをチェックしてください:ストリームの変更

2018年1月10日- リリース3.6

*編集:これを行う方法についての記事を書きましたhttps://medium.com/riow/mongodb-data-collection-change-85b63d96ff76

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


mongodb 3.6 の新機能https://docs.mongodb.com/manual/release-notes/3.6/ 2018/01/10

$ mongod --version
db version v3.6.2

changeStreamsを使用するには、データベースがレプリケーションセットである必要があります

レプリケーションセットの詳細:https : //docs.mongodb.com/manual/replication/

データベースはデフォルトで「スタンドアロン」になります。

スタンドアロンをレプリカセットに変換する方法: https //docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/


次のは、これをどのように使用するかについての実用的なアプリケーションです。
*特にノードの場合。

/* 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 */

役立つリンク: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


すべての編集について申し訳ありませんが、SOは私の "リンク"を好きではありませんでした(不適切にフォーマットされたコードであると言いました)
Rio Weber

1
データベースにクエリを実行する必要はないはずです。watch()などで、新しいデータをリッスンしているサーバーに送信できます
Alexander Mills

22

MongoDBバージョン3.6には、本質的にはOpLogの上にあるAPIである変更ストリームが含まれており、トリガー/通知のような使用例を可能にします。

Javaの例へのリンクは次のとおりです。 。http //mongodb.github.io/mongo-java-driver/3.6/driver/tutorials/change-streams/

NodeJSの例は次のようになります。

 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));
          });
      });

JSON.stringifyが...アンドロイドスタジオ(Androidのアプリケーション)でこのデータを受信することは非常に重要である
DragonFire

3

または、標準のMongo FindAndUpdateメソッドを使用し、コールバック内で、コールバックの実行時に(ノード内の)EventEmitterイベントを発生させることができます。

このイベントをリッスンするアプリケーションまたはアーキテクチャの他の部分には、更新が通知され、関連するデータもそこに送信されます。これは、Mongoからの通知を取得するための本当に簡単な方法です。


これは非常に非効率的です。FindAndUpdateごとにデータベースをロックしています。
Yash Gupta

1
私の推測では、Alexは少し異なる(特に挿入に対応していない)が、キューに入れられたジョブの状態が変化したときにクライアントに何らかの通知を送信する方法など、関連する質問に答えていたと思われます。 、正常に完了するか失敗します。websocketを使用してノードに接続されたクライアントでは、状態変更メッセージを受信したときに呼び出すことができるFIndAndUpdateコールバックのブロードキャストイベントで、すべてのクライアントに変更を通知できます。更新を行う必要があるため、これは非効率的ではないと思います。
Peter Scott

3

これらの回答の多くは、新しいレコードのみを提供し、更新は提供しないか、非常に非効率的です。

これを行うための唯一の信頼性の高い、パフォーマンスの高い方法は、ローカルdb:oplog.rsコレクションに調整可能なカーソルを作成して、MongoDBへのすべての変更を取得し、それを実行することです。(MongoDBは、レプリケーションをサポートするために、内部的に多かれ少なかれこれを実行します!)

oplogの内容の説明:https ://www.compose.com/articles/the-mongodb-oplog-and-node-js/

oplogで実行できることに関するAPIを提供するNode.jsライブラリの例:https : //github.com/cayasso/mongo-oplog


2

MongoDB Stitchと呼ばれる素晴らしいサービスのセットがあります。見ステッチ機能/トリガ。これはクラウドベースの有料サービス(AWS)であることに注意してください。あなたの場合、挿入時に、JavaScriptで記述されたカスタム関数を呼び出すことができます。

ここに画像の説明を入力してください


stackoverflow.com/users/486867/manish-jain-データがテーブルに挿入されたことをREACTアプリケーションに通知するためにステッチを使用する方法の例はありますか?
MLissCetrus

1

ここで見つけることができる実用的なJavaの例があります

 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();

ここで重要なのはQUERY OPTIONSです。

また、毎回すべてのデータをロードする必要がない場合は、検索クエリを変更できます。

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

実際には、出力を監視する代わりに、mongooseスキーマによって提供されるミドルウェアを使用して新しいものが挿入されたときに通知を受け取らない理由

新しいドキュメントを挿入するイベントをキャッチし、この挿入が完了した後に何かを行うことができます


私の悪い。申し訳ありませんでした。
Duong Nguyen

0

3.6以降では、データベースの使用が許可され、次のデータベーストリガータイプが使用されます。

  • イベント駆動型トリガー-関連ドキュメントを自動的に更新し、ダウンストリームサービスに通知し、データを伝達して混合ワークロード、データの整合性と監査をサポートするのに役立ちます
  • スケジュールされたトリガー-スケジュールされたデータの取得、伝播、アーカイブ、分析のワークロードに役立ちます

AtlasアカウントにログインしてTriggersインターフェースを選択し、新しいトリガーを追加します。

ここに画像の説明を入力してください

その他の設定や詳細については、各セクションを展開してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.