Mongodb $ lookup聚合查询需要11秒以上,我该如何优化它

时间:2018-02-27 12:39:49

标签: mongodb mongoose mongodb-query aggregation-framework

我有一个有3个收藏品的股票价格警报应用程序

1)提醒

{
    "_id" : ObjectId("5a9543c235434a185038e778"), //alert id
    "1" : ObjectId("5a9543c035434a185038e743"), //user id
    "2" : "xyz", //asset id
    "3" : "EUR", //fiat set by the user
    "4" : NumberDecimal("1000"), //price at which user wants alert
    "5" : true, // true if 1000 was greater than price at the time of setting the alert
    "6" : 0, //type 0 indicating it is a price alert
    "__v" : 0
}

2)美元资产价值每2分钟更新一次,目前 2000 资产

{
    "_id" : "xyz", //asset id
    "1" : 997, //current price of the asset
}

3)每小时更新美元的菲亚特转换,目前 160 货币

{
    "_id" : "EUR",
    "1" : 0.811798
}

一个人必须能够设置一个警告“如果欧元中的xyz低于1000或高于1000,则提醒我”

对于测试数据,我设置了100000个警报,第一步是从我为其加入的资产表中找到资产xyz的当前价格

 Alert
        .aggregate([
            {
                $lookup: {
                    from: "assets",
                    localField: "2", //field containing asset id
                    foreignField: "_id", //foreign field with asset id
                    as: "s"
                }

            }
        ])

        .allowDiskUse(true)

        .exec((error, result) => {

            if (error) {
                console.log(error)
            }
            else {
                console.log("Got", result.length, "documents mongoose")
            }
            mongoose.connection.close()
        })

目前仅需11秒!

我希望能够将每种资产的当前价格乘以指定的货币,然后检查它是否高于或低于用户设定的水平,以便触发警报

例如,如果警报是xyz:EUR,我希望以美元获得XYZ的资产价格,将美元的价格变为欧元并乘以两者以获得最终价格xyz:EUR并检查该值是否更大小于或小于1000来触发警报

外部字段是一个_id字段,我假设它是默认索引的。我在localField 2上设置了一个索引:这是我的资产ID和3:这是我的法定符号

以下是我的目标

getIndexes()查询的结果
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.alerts"
    },
    {
        "v" : 2,
        "key" : {
            "2" : 1
        },
        "name" : "2_1",
        "ns" : "test.alerts",
        "background" : true
    },
    {
        "v" : 2,
        "key" : {
            "3" : 1
        },
        "name" : "3_1",
        "ns" : "test.alerts",
        "background" : true
    }
]

我还运行了一个 explain()来检查发生了什么,尽管设置了索引,但它表明了一个COLLSCAN

{
        "stages": [
                {
                        "$cursor": {
                                "query": {},
                                "queryPlanner": {
                                        "plannerVersion": 1,
                                        "namespace": "test.alerts",
                                        "indexFilterSet": false,
                                        "parsedQuery": {},
                                        "winningPlan": {
                                                "stage": "COLLSCAN",
                                                "direction": "forward"
                                        },
                                        "rejectedPlans": []
                                }
                        }
                },
                {
                        "$lookup": {
                                "from": "assets",
                                "as": "s",
                                "localField": "2",
                                "foreignField": "_id"
                        }
                }
        ],
        "ok": 1
}

任何建议,建议都会非常有帮助。谢谢

替代

我真的不想做的另一个选择是计算代码中的所有价格,并在警报集合中添加另一个字段,称为价格,每隔2分钟更新一次xyz:EUR和其他警报。潜在的1500资产x 160菲律宾将意味着每2分钟就有很多条目可以突破

更新1个Profiler输出

密钥未被使用!任何想法

getmore test.e1_sources 89ms Tue Feb 27 2018 19:22:28
command:{
    "getMore" : NumberLong("6524439989055389783"),
    "collection" : "alerts",
    "batchSize" : 1000,
    "$readPreference" : {
        "mode" : "secondaryPreferred"
    },
    "$db" : "test"
} originatingCommand:{
    "aggregate" : "alerts",
    "pipeline" : [
        {
            "$lookup" : {
                "from" : "assets",
                "localField" : "2",
                "foreignField" : "_id",
                "as" : "s"
            }
        }
    ],
    "allowDiskUse" : true,
    "cursor" : {
        "batchSize" : 1000
    },
    "$db" : "test"
} cursorid:NumberLong("6524439989055389783") keysExamined:0 docsExamined:0 cursorExhausted numYield:2 locks:{
    "Global" : {
        "acquireCount" : {
            "r" : NumberLong(6000)
        }
    },
    "Database" : {
        "acquireCount" : {
            "r" : NumberLong(3000)
        }
    },
    "Collection" : {
        "acquireCount" : {
            "r" : NumberLong(2999)
        }
    }
} nreturned:1000 responseLength:164393 protocol:op_query planSummary:COLLSCAN client:127.0.0.1 allUsers:[ ] user: 

1 个答案:

答案 0 :(得分:1)

一个人必须能够设置一个警告“如果欧元中的xyz低于1000或高于1000,则提醒我”

根据您提到的上述行。添加一个匹配阶段来过滤该用户ID的assetid。这将减少你必须加入的数据量。

Alert.aggregate([
            {
                $match:{
                     "1"://user id,
                     "2"://asset id
                     }
            },
            {
                $lookup: {
                    from: "assets",
                    localField: "2", //field containing asset id
                    foreignField: "_id", //foreign field with asset id
                    as: "s"
                }

            }
        ])

        .allowDiskUse(true)

        .exec((error, result) => {

            if (error) {
                console.log(error)
            }
            else {
                console.log("Got", result.length, "documents mongoose")
            }
            mongoose.connection.close()
        })