【问题标题】:MongoDB + Python - very slow simple queryMongoDB + Python - 非常慢的简单查询
【发布时间】:2014-02-05 05:22:48
【问题描述】:

我有一个开源能源监视器 (http://openenergymonitor.org),它每五秒记录一次我家的用电量,所以我认为这将是一个完美的应用程序来使用 MongoDB。我有一个在 Apache 中运行的 Flask Python 应用程序,使用 MongoEngine 与 MongoDB 交互。

现在我在 RaspberryPi 上运行所有这些,所以我并不期待令人难以置信的性能,但是一个简单的查询大约需要 20 秒,即使对于这种有限的硬件来说,这似乎也很慢。

我有以下型号:

class Reading(db.Document):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    created_at_year = db.IntField(default=datetime.datetime.now().year, required=True)
    created_at_month = db.IntField(default=datetime.datetime.now().month, required=True)
    created_at_day = db.IntField(default=datetime.datetime.now().day, required=True)
    created_at_hour = db.IntField(default=datetime.datetime.now().hour, required=True)
    battery = db.IntField()
    power = db.IntField()
    meta = {
        'indexes': ['created_at_year', 'created_at_month', 'created_at_day', 'created_at_hour']
    }

我目前存储了过去几天的大约 36,000 个读数。以下代码运行速度超快:

def get_readings_count():
    count = '<p>Count: %d</p>' % Reading.objects.count()
    return count

def get_last_24_readings_as_json():
    readings = Reading.objects.order_by('-id')[:24]
    result = "["
    for reading in reversed(readings):
        result += str(reading.power) + ","
    result = result[:-1]
    result += "]"
    return result

但是做一个简单的过滤器:

def get_today_readings_count():
    todaycount = '<p>Today: %d</p>' % Reading.objects(created_at_year=2014, created_at_month=1, created_at_day=28).count()
    return todaycount

大约需要 20 秒 - 今天大约有 11,000 个读数。

我应该放弃对我的 Pi 更多的期待,还是我可以做一些调整以从 MongoDB 获得更高的性能?

Debian Wheezy 上的 Mongo 2.1.1

2014 年 1 月 29 日更新:

针对下面的回答,下面是 getIndexes() 和 explain() 的结果:

> db.reading.getIndexes()
[
    {
        "v" : 1,
        "key" : {
            "_id" : 1
        },
        "ns" : "sensor_network.reading",
        "name" : "_id_"
    },
    {
        "v" : 1,
        "key" : {
            "created_at_year" : 1
        },
        "ns" : "sensor_network.reading",
        "name" : "created_at_year_1",
        "background" : false,
        "dropDups" : false
    },
    {
        "v" : 1,
        "key" : {
            "created_at_month" : 1
        },
        "ns" : "sensor_network.reading",
        "name" : "created_at_month_1",
        "background" : false,
        "dropDups" : false
    },
    {
        "v" : 1,
        "key" : {
            "created_at_day" : 1
        },
        "ns" : "sensor_network.reading",
        "name" : "created_at_day_1",
        "background" : false,
        "dropDups" : false
    },
    {
        "v" : 1,
        "key" : {
            "created_at_hour" : 1
        },
        "ns" : "sensor_network.reading",
        "name" : "created_at_hour_1",
        "background" : false,
        "dropDups" : false
    }
]

> db.reading.find({created_at_year: 2014, created_at_month: 1, created_at_day: 28 }).explain()
{
    "cursor" : "BtreeCursor created_at_day_1",
    "isMultiKey" : false,
    "n" : 15689,
    "nscannedObjects" : 15994,
    "nscanned" : 15994,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 5,
    "nChunkSkips" : 0,
    "millis" : 25511,
    "indexBounds" : {
        "created_at_day" : [
            [
                28,
                28
            ]
        ]
    },
    "server" : "raspberrypi:27017"
}

2 月 4 日更新

好的,所以我删除了索引,在 created_at 上设置了一个新索引,删除了所有记录并留了一天时间来收集新数据。我刚刚对今天的数据进行了查询,并且花费了更长的时间(48 秒):

> db.reading.find({'created_at': {'$gte':ISODate("2014-02-04")}}).explain()
{
    "cursor" : "BtreeCursor created_at_1",
    "isMultiKey" : false,
    "n" : 14189,
    "nscannedObjects" : 14189,
    "nscanned" : 14189,
    "scanAndOrder" : false,
    "indexOnly" : false,
    "nYields" : 9,
    "nChunkSkips" : 0,
    "millis" : 48653,
    "indexBounds" : {
        "created_at" : [
            [
                ISODate("2014-02-04T00:00:00Z"),
                ISODate("292278995-12-2147483314T07:12:56.808Z")
            ]
        ]
    },
    "server" : "raspberrypi:27017"
}

数据库中只有 16,177 条记录和一个索引。大约有 111MB 的可用内存,所以索引适合内存应该没有问题。我想我将不得不将其注销,因为 Pi 的功能不足以胜任这项工作。

【问题讨论】:

    标签: python mongodb mongoengine


    【解决方案1】:

    您确定您的索引已创建吗?你能提供你收藏的getIndexes()的输出吗

    例如:db.my_collection.getIndexes()

    以及您的查询说明

    db.my_collection.find({created_at_year: 2014, created_at_month: 1, created_at_day: 28 }).explain()
    

    PS:当然,我必须同意 @Aesthete 的观点,即你存储的东西比你需要的多得多……

    2014 年 1 月 29 日更新

    完美!如您所见,当您可以创建一个包含所有索引的复合索引时,您有四个不同的索引。

    定义

    db.my_collection.ensureIndex({created_at_year: 1, created_at_month: 1, created_at_day: 1, created_at_hour: 1 })

    将为您提供更精确的索引,使您能够查询:

    • year
    • yearmonth
    • yearmonthday
    • yearmonthdayhour

    这将使您的查询(使用四个键)更快,因为您的所有条件都将在索引数据中得到满足!

    请注意ensureIndex() 中键的顺序至关重要,该顺序实际上定义了上述查询列表!

    另请注意,如果您只需要这 4 个字段,那么如果您指定正确的投影
    例如:
    db.my_collection.find({created_at_year: 2014, created_at_month: 1, created_at_day: 28}, { created_at_year: 1, created_at_month: 1, created_at_day: 1 })

    那么只会使用索引,这是最大的性能!

    【讨论】:

    • 感谢您的回复,我已经用结果更新了我的帖子。
    • 再次感谢 - 我还没有添加复合索引,但是已经重新运行了仅针对一个索引的解释查询:db.reading.find({created_at_day: 28 }).explain () 这仍然持续到 13 秒。这是我能期待的最好表现吗?
    • @littlecharva 它取决于,很可能是的,除非你想要的只是 created_at_day 字段(或者通常是复合索引上的键),在这种情况下查询不需要查db上的数据,只会在index里找,最快可以查到!
    【解决方案2】:

    可能与您将日期保存 5 次有关 保存一次(即保留 created_at),然后如果您希望在视图中显示月、日等,只需将 created_at 值转换为仅显示月、日等

    【讨论】:

    • 我将日期时间分解为其组成部分,因为我计划使用 mapreduce 以各种方式聚合数据,因此不必在每个地图函数中提取日期或小时,它已经存在供我使用。
    • 尝试使用刚刚创建的_at,并编写函数从该值中获取日期、月份等,这样做可能会提高性能,您可以对这两种方法计时,看看哪一种更好,您可能会偶然发现最佳平衡。我还应该补充一点,按照我提到的方式进行操作所需的数据库命中次数比您使用的方式要少
    【解决方案3】:

    我想知道索引是否不适合您的树莓派的记忆。由于 MongoDB 每个查询只能使用一个索引,而且似乎只使用 created_by_day 查询,您可以尝试删除索引并用created_at 时间戳上的索引替换它们。然后,您可以通过删除 created_at_* 字段来减小文档的大小。

    您可以在 map reduce 函数或聚合框架 date operators 中轻松地从 ISO 日期中提取日、月、年等。

    today 的查询会变成这样:

    db.reading.find({'created_at':{'$gte':ISODate("2014-01-29"), '$lt':ISODate("2014-01-30")}})
    

    我认为有趣的是,您选择了一个宣传为适合在您的嵌入式设备上运行大数据的数据库。我很好奇它会如何工作。我有一个类似的小工具,并使用BerkeleyDB 来存储读数。不要忘记 32 位操作系统上的 MongoDB 整个数据库的最大大小为 2GB。

    【讨论】:

    • 查看我对原始帖子的更新,了解使用一个索引的结果。我选择使用 BIG DATA 数据库,因为我想使用它,感觉就像每 5 秒读取一次传感器,使用低功耗设备几乎就像一个迷你大数据项目。我会研究一下 BerkeleyDB,谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-01
    • 2020-11-21
    • 2012-04-01
    • 2020-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多