2014-01-28 189 views
0

我有一個開源的能源監視器(http://openenergymonitor.org),它每五秒記錄我家的用電量,所以我認爲這將是一個完美的應用程序,可以與MongoDB一起玩。我有一個使用MongoEngine與MongoDB進行交互的Flask Python應用程序。MongoDB + Python - 非常慢的簡單查詢

現在我正在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'] 
    } 

我目前已經從過去幾天的存儲各地36000讀數。下面的代碼運行超快:

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獲得更多的性能?

蒙戈2.1.1在Debian喘息

更新29/1/2014:

針對下面的答案,這裏是getIndexes的結果(上)並解釋():

> 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" 
} 

與僅16177數據庫中的記錄,只有一個指標的。大約有111MB的可用內存,因此內存中的索引擬合應該沒有問題。我想我將不得不寫這篇文章,因爲Pi沒有足夠強大的工作。

回答

0

可能與你一起保存日期5次 保存一次(即保留created_at),然後如果你想在你的視圖中的月份,日期等,只需將created_at值轉換爲顯示月份,日期etc

+0

我將日期時間分解爲其組成部分,因爲我打算用mapreduce以各種方式聚合數據,而不是必須在每個地圖功能中提取日期或小時,它已經在那裏供我使用。 – littlecharva

+0

嘗試並使用created_at,並編寫函數以從該值中獲取日期,月份等,這樣做可以提高性能,您可以計時兩種方法並查看哪種方法更好,您可能偶然發現最佳平衡 。我還應該補充一下,按照提到的方式,要求的數據庫命中次數少於完成的方式 – Aesthete

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
  • 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 })

然後只索引將被使用,這是最大的性能!

+0

感謝您的回覆,我已經使用結果更新了我的帖子。 – littlecharva

+0

@littlecharva檢查我的更新。 – xlembouras

+0

再次感謝 - 我還沒有添加複合索引,但重新運行解釋查詢再次只針對一個索引:db.reading.find({created_at_day:28})。explain()並且仍然運行13秒。這是我能期待的最佳表現嗎? – littlecharva

0

我不知道這些指標是否適合您的覆盆子pi的記憶。由於MongoDB只能爲每個查詢使用一個索引,並且它似乎只使用created_by_day查詢,所以您可以嘗試刪除索引並用created_at時間戳上的索引替換它們。然後,您可以通過刪除created_at_*字段來縮小文檔的大小。

您可以在地圖縮小功能中使用ISO日期或使用聚合框架date operators輕鬆提取日期,月份,年份等。

today查詢就變成了這樣的事:

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

我認爲這是有趣的是,你選擇了一個數據庫標榜適合大數據到嵌入式設備上運行。我很好奇它是如何運作的。我有一個類似的小工具,並使用BerkeleyDB來存儲讀數。不要忘記,32位操作系統上的MongoDB對於整個數據庫的最大容量爲2GB。

+0

查看我對原始帖子的更新,瞭解使用一個索引的結果。我選擇使用BIG DATA數據庫,因爲我想與它一起玩,感覺每5秒鐘就有一個傳感器讀數,使用低功耗設備就像是一個小型大數據項目。我會研究BerkeleyDB,謝謝。 – littlecharva