Yuanchieh's Blog

Yuanchieh's Blog

生命是長期而持續的累積

06 Feb 2020

MongoDB 批次處理大量數據

本篇分享 MongoDB 在批次查訊大量數據時的小技巧

在查詢小筆數據時,往往我們就是用 find().toArray() 直接回傳結果,需要簡單的分頁就搭配 limit() / skip() 即可完成
但如果需要分析數據跑過整個 collection,不可能用 find() 一次拉回所有的資料,透過 skip() 批次處理,如果 collection 資料不多還好,如果幾十萬筆、幾百萬筆資料效能回非常的低落,因為 skip 的話每次查詢 DB 都必須要重頭開始跳過指定筆數資料,才能回傳,所以查詢時間會隨著筆數增加而趨近於指數倍增長

目前已知有兩種做法,可以有效輪詢整個 collection 而不拖垮 DB 效能
一種是 Cursor,但是在實務上我個人並沒有採用,原因是 Cursor 每次只能透過 .next() 取得一筆資料,如果希望一次拉個上千筆資料處理就無法做到;
另一種是本次要分享的方式,透過遞迴查詢,保持性能情況下跑完整個 Collection,實務上每天應用在有千萬筆資料的 Collection 而沒有太大的問題

遞回查詢

說穿了其實很簡單,透過有建立 index 的索引,按照遞增或遞減排序,每批次取出部分數據後,下次查詢的條件改為取出數據的最後一位,一路到 Collection 結束

以下是程式碼部分

const MongoClient = require('mongodb').MongoClient;

const dbUrl = "mongodb://127.0.0.1:27017";
MongoClient.connect(dbUrl, async function (err, client) {
    console.log(err)
    const testDB = client.db("test");
    const userCollection = testDB.collection("users");

    const result = await iterateCollection({
        sourceCollection: userCollection,
        query: {
            age: 20
        },
        batchSize: 10,
        order: "asc"
    });

    console.log(result);

    client.close();
});

async function iterateCollection({
    sourceCollection,
    query,
    batchSize,
    order
}) {
    let result = [];
    let sort = {
        _id: -1
    }
    let _query = {
        ...query
    };

    if (order === "asc") {
        sort = {
            _id: 1
        }
    }


    while (true) {
        const queryResults = await sourceCollection.find(_query).limit(batchSize).sort(sort).toArray();

        if(queryResults.length === 0){
            break;
        }

        _query._id = {
            $lt: queryResults[queryResults.length - 1]._id
        }
        if (order === "asc") {
            _query._id = {
                $gt: queryResults[queryResults.length - 1]._id
            }
        }

        // process data and push to result
        // result.push(queryResults.map(result => result._id));
    }

    return result;
}

Categories