sql >> データベース >  >> NoSQL >> MongoDB

MongoDB-エラー:getMoreコマンドが失敗しました:カーソルが見つかりません

    編集-クエリのパフォーマンス:

    @NeilLunnがコメントで指摘したように、ドキュメントを手動でフィルタリングするのではなく、.find(...)を使用する必要があります。 その代わりに:

    db.snapshots.find({
        roundedDate: { $exists: true },
        stream: { $exists: true },
        sid: { $exists: false }
    })
    

    また、.bulkWrite()を使用します 、MongoDB 3.2から入手可能 、個別の更新を行うよりもはるかにパフォーマンスが高くなります。

    これにより、カーソルの有効期間10分以内にクエリを実行できる可能性があります。それでもそれ以上かかる場合は、カーソルが期限切れになり、とにかく同じ問題が発生します。これについては、以下で説明します。

    ここで何が起こっているのか:

    Error: getMore command failed 2つのカーソル属性に関連するカーソルタイムアウトが原因である可能性があります。

    • タイムアウト制限。デフォルトでは10分です。ドキュメントから:

      デフォルトでは、サーバーは、非アクティブ状態が10分間続くか、クライアントがカーソルを使い果たした場合に、カーソルを自動的に閉じます。

    • バッチサイズ。最初のバッチでは101ドキュメントまたは16MBであり、後続のバッチではドキュメントの数に関係なく16 MBです(MongoDB 3.4以降)。 )。ドキュメントから:

      find() およびaggregate() 操作の初期バッチサイズは、デフォルトで101ドキュメントです。結果のカーソルに対して発行された後続のgetMore操作にはデフォルトのバッチサイズがないため、16メガバイトのメッセージサイズによってのみ制限されます。

    おそらく、最初の101個のドキュメントを消費してから、最大の16 MBのバッチを取得し、さらに多くのドキュメントを取得している可能性があります。それらの処理に10分以上かかるため、サーバー上のカーソルがタイムアウトになり、2番目のバッチのドキュメントの処理が完了して新しいドキュメントを要求するまでに、カーソルはすでに閉じられています。

    カーソルを繰り返し処理して返されたバッチの最後に到達すると、さらに結果がある場合、cursor.next()はgetMore操作を実行して次のバッチを取得します。

    考えられる解決策:

    これを解決するための5つの可能な方法、長所と短所を備えた3つの良い方法、および2つの悪い方法があります。

    1. 👍カーソルを存続させるためにバッチサイズを縮小します。

    2. 👍カーソルからタイムアウトを削除します。

    3. 👍カーソルの期限が切れたら再試行してください。

    4. 👎結果をバッチで手動でクエリします。

    5. 👎カーソルが切れる前にすべてのドキュメントを取得します。

    特定の基準に従って番号が付けられていないことに注意してください。それらを読み、特定のケースに最適なものを決定してください。

    1。 👍カーソルを存続させるためにバッチサイズを縮小する

    これを解決する1つの方法は、cursor.bacthSizeを使用することです。 findによって返されるカーソルのバッチサイズを設定します 10分以内に処理できるものと一致するクエリ:

    const cursor = db.collection.find()
        .batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);
    

    ただし、非常に控えめな(小さい)バッチサイズの設定はおそらく機能しますが、サーバーに何度もアクセスする必要があるため、速度も遅くなることに注意してください。

    一方、10分で処理できるドキュメント数に近すぎる値に設定すると、何らかの理由で一部の反復の処理に少し時間がかかる可能性があります(他のプロセスはより多くのリソースを消費する可能性があります) 、とにかくカーソルが期限切れになり、同じエラーが再び発生します。

    2。 👍カーソルからタイムアウトを削除します

    もう1つのオプションは、cursor.noCursorTimeoutを使用して、カーソルがタイムアウトしないようにすることです。

    const cursor = db.collection.find().noCursorTimeout();
    

    カーソルを手動で閉じるか、すべての結果を使い果たして自動的に閉じる必要があるため、これは悪い習慣と見なされます:

    noCursorTimeoutを設定した後 オプションの場合は、cursor.close()を使用してカーソルを手動で閉じる必要があります。 またはカーソルの結果を使い果たします。

    カーソル内のすべてのドキュメントを処理するため、手動で閉じる必要はありませんが、コードで他の問題が発生し、完了する前にエラーがスローされて、カーソルが開いたままになる可能性があります。 。

    それでもこのアプローチを使用したい場合は、try-catchを使用してください すべてのドキュメントを消費する前に、問題が発生した場合は必ずカーソルを閉じてください。

    悪い習慣と見なされていても、これを悪い解決策とは見なさないことに注意してください(したがって👍)...:

    • ドライバーがサポートする機能です。他の解決策で説明されているように、タイムアウトの問題を回避する別の方法があるため、それが非常に悪かった場合、これはサポートされません。

    • 安全に使用する方法はいくつかありますが、それは特に注意が必要です。

    • この種のクエリを定期的に実行していないと思います。そのため、どこでもカーソルを開いたままにしておく可能性は低くなります。これが当てはまらず、これらの状況に常に対処する必要がある場合は、noCursorTimeoutを使用しないのが理にかなっています。 。

    3。 👍カーソルの期限が切れたら再試行してください

    基本的に、コードをtry-catchに配置します エラーが発生すると、すでに処理したドキュメントをスキップする新しいカーソルが表示されます。

    let processed = 0;
    let updated = 0;
    
    while(true) {
        const cursor = db.snapshots.find().sort({ _id: 1 }).skip(processed);
    
        try {
            while (cursor.hasNext()) {
                const doc = cursor.next();
    
                ++processed;
    
                if (doc.stream && doc.roundedDate && !doc.sid) {
                    db.snapshots.update({
                        _id: doc._id
                    }, { $set: {
                        sid: `${ doc.stream.valueOf() }-${ doc.roundedDate }`
                    }});
    
                    ++updated;
                } 
            }
    
            break; // Done processing all, exit outer loop
        } catch (err) {
            if (err.code !== 43) {
                // Something else than a timeout went wrong. Abort loop.
    
                throw err;
            }
        }
    }
    

    このソリューションを機能させるには、結果を並べ替える必要があることに注意してください。

    このアプローチでは、10分間に処理できるドキュメントの数を事前に推測することなく、可能な最大バッチサイズである16 MBを使用して、サーバーへのリクエスト数を最小限に抑えることができます。したがって、以前のアプローチよりも堅牢です。

    4。 👎結果をバッチで手動でクエリします

    基本的に、skip()、limit()、sort()を使用して、10分で処理できると思われる多数のドキュメントに対して複数のクエリを実行します。

    ドライバーにはすでにバッチサイズを設定するオプションがあるため、これは悪い解決策だと思います。手動でこれを行う理由はありません。解決策1を使用し、車輪の再発明を行わないでください。

    また、ソリューション1と同じ欠点があることにも言及する価値があります

    5。 👎カーソルが切れる前にすべてのドキュメントを取得します

    おそらく、結果の処理のためにコードの実行に時間がかかるため、最初にすべてのドキュメントを取得してから処理することができます。

    const results = new Array(db.snapshots.find());
    

    これにより、すべてのバッチが次々に取得され、カーソルが閉じます。次に、results内のすべてのドキュメントをループできます 必要なことをします。

    ただし、タイムアウトの問題が発生している場合は、結果セットが非常に大きい可能性があるため、メモリ内のすべてをプルすることは、最も推奨される方法ではない可能性があります。

    スナップショットモードと重複ドキュメントに関する注意

    ドキュメントサイズの増加により、書き込み操作の間にドキュメントが移動した場合、一部のドキュメントが複数回返される可能性があります。これを解決するには、cursor.snapshot()を使用します 。ドキュメントから:

    カーソルにsnapshot()メソッドを追加して、「スナップショット」モードを切り替えます。これにより、書き込み操作の間にドキュメントサイズが大きくなったためにドキュメントが移動した場合でも、クエリがドキュメントを複数回返すことはありません。

    ただし、その制限に注意してください:

    • シャーディングされたコレクションでは機能しません。

    • sort()では機能しません またはhint() 、したがって、ソリューション3および4では機能しません。

    • 挿入または削除からの分離を保証するものではありません。

    ソリューション5では、重複するドキュメントの取得を引き起こす可能性のあるドキュメントの移動の時間枠が他のソリューションよりも狭いため、snapshot()は必要ない場合があることに注意してください。 。

    あなたの特定のケースでは、コレクションはsnapshotと呼ばれています 、おそらく変更される可能性は低いので、おそらくsnapshot()は必要ありません。 。さらに、データに基づいてドキュメントを更新しているため、更新が完了すると、ifのように、同じドキュメントが複数回取得されても、同じドキュメントが再度更新されることはありません。 条件はそれをスキップします。

    開いているカーソルに関する注意

    開いているカーソルの数を確認するには、db.serverStatus().metrics.cursorを使用します 。



    1. $ inには、2番目の引数として配列が必要です。

    2. mongodbで$lookupを使用して複数のコレクションに参加する方法

    3. MongoDBデータベースの暗号化

    4. ローカルのmongodbを起動/起動できません