MongoDB + Python - 非常慢的简单查询

时间:2014-01-28 20:29:51

标签: python mongodb mongoengine

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

更新29/1/2014:

在回答下面的答案时,以下是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对于这项工作不够强大。

3 个答案:

答案 0 :(得分: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关于你存储的东西比你需要的多得多......

29/1/2014更新

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

定义

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

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

  • year
  • yearmonth
  • year以及monthday
  • yearmonth以及dayhour

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

请注意,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 })

然后只使用索引,这是最高性能!

答案 1 :(得分:0)

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

答案 2 :(得分:0)

我想知道索引是否适合你的覆盆子pi的记忆。由于MongoDB每个查询只能使用一个索引,并且它似乎只使用created_by_day查询,因此您可以尝试删除索引并将其替换为created_at时间戳上的索引。然后,您可以通过删除created_at_*字段来减小文档的大小。

您可以轻松地从地图缩减功能或聚合框架date operators中的ISO日期中提取日,月,年等。

today的查询会变成这样:

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

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