在上一篇博客文章時間序列數據與MongoDB:第一部分-簡介中,咱們介紹了時間序列數據的概念,而後介紹了一些能夠用於幫助收集時間序列應用程序需求的發現問題。對這些問題的回答有助於指導支持大容量生產應用程序部署所需的模式和MongoDB數據庫配置。在這篇博客文章中,咱們將重點討論在讀、寫、更新和刪除操做下,兩種不一樣的模式設計如何影響內存和磁盤利用率。數據庫
在分析結束時,您可能會發現應用程序的最佳模式設計多是利用模式設計的組合。經過遵循咱們下面列出的建議,您將有一個良好的起點來爲您的應用程序開發最佳模式設計,並適當調整您的環境大小。設計模式
讓咱們首先說明,沒有一種規範模式設計能夠適用於全部應用程序場景。不管您開發的模式是什麼,都要考慮權衡。理想狀況下,您但願得到最佳的內存和磁盤利用率平衡,以產生最佳的讀寫性能,從而知足應用程序的需求,並支持數據攝取和時間序列數據流的分析。api
在這篇博客文章中,咱們將討論各類模式設計配置。首先,爲每一個數據樣本存儲一個文檔,而後使用每一個時間序列時間範圍的一個文檔和每一個固定大小的文檔對數據進行封裝。爲每一個文檔存儲一個以上的數據樣本稱爲bucketing。這將在應用程序級別實現,不須要在MongoDB中進行任何配置。有了MongoDB靈活的數據模型,你能夠優化存儲你的數據,爲你的應用程序的需求提供最好的性能和粒度。緩存
這種靈活性還容許數據模型隨着時間的推移適應新的需求——好比從不屬於原始應用程序設計的新硬件傳感器捕獲數據。這些新的傳感器提供了與原始設計中使用的傳感器不一樣的元數據和屬性。有了這些靈活性,您可能會認爲MongoDB數據庫是蠻荒的西部,任何東西均可以,您很快就會獲得一個充滿無序數據的數據庫。MongoDB經過模式驗證提供了您所須要的儘量多的控制,容許您徹底控制執行強制字段的存在和可接受值的範圍等等。服務器
爲了幫助演示模式設計和bucketing如何影響性能,請考慮咱們想要存儲和分析歷史股票價格數據的場景。咱們的示例股票價格生成器應用程序每秒鐘爲它跟蹤的給定數量的股票建立示例數據。1秒是本例中爲每一個股票行情收集的數據的最小時間間隔。若是您想在本身的環境中生成示例數據,能夠在GitHub上使用StockGen工具。須要注意的是,儘管本文中的示例數據使用股票刻度做爲示例,但您能夠將這些相同的設計概念應用於任什麼時候間序列場景,好比物聯網傳感器的溫度和溼度讀數。架構
用於生成示例數據的StockGen工具將生成相同的數據,並將其存儲在兩個不一樣的集合中:StockDocPerSecond和StockDocPerMinute,它們各自包含如下模式:機器學習
{
"_id" : ObjectId("5b4690e047f49a04be523cbd"),
"p" : 56.56,
"symbol" : "MDB",
"d" : ISODate("2018-06-30T00:00:01Z")
},
{
"_id" : ObjectId("5b4690e047f49a04be523cbe"),
"p" : 56.58,
"symbol" : "MDB",
"d" : ISODate("2018-06-30T00:00:02Z")
},
...ide
{
"_id" : ObjectId("5b5279d1e303d394db6ea0f8"),
"p" : {
"0" : 56.56,
"1" : 56.56,
"2" : 56.58,
…
"59" : 57.02
},
"symbol" : "MDB",
"d" : ISODate("2018-06-30T00:00:00Z")
},
{
"_id" : ObjectId("5b5279d1e303d394db6ea134"),
"p" : {
"0" : 69.47,
"1" : 69.47,
"2" : 68.46,
...
"59" : 69.45
},
"symbol" : "TSLA",
"d" : ISODate("2018-06-30T00:01:00Z")
},
...工具
請注意,字段「p」包含一個子文檔,其中包含每分鐘每秒的值。性能
讓咱們根據StockGen工具生成的4周的數據來比較和對比存儲大小和內存影響的數據庫指標。在評估數據庫性能時,度量這些指標很是有用。
在咱們的應用程序中,時間粒度的最小級別是秒。場景1中描述的每秒存儲一個文檔對於那些來自關係數據庫背景的人來講是最合適的模型概念。這是由於咱們爲每一個數據點使用一個文檔,這相似於表格模式中爲每一個數據點使用一行。這種設計將在單位時間內產生最大數量的文檔和集合大小,如圖3和圖4所示。
圖4顯示了每一個集合的兩種大小。這個系列中的第一個值是存儲在磁盤上的集合的大小,而第二個值是數據庫中數據的大小。這些數字是不一樣的,由於MongoDB的WiredTiger存儲引擎支持靜態數據壓縮。邏輯上每秒的收集是605MB,可是在磁盤上,它消耗了大約190 MB的存儲空間。
大量的文檔不只會增長數據存儲的消耗,還會增長索引的大小。在每一個集合上建立索引,覆蓋符號和日期字段。與一些將本身定位爲時間序列數據庫的鍵值數據庫不一樣,MongoDB提供了二級索引,使您可以靈活地訪問數據,並容許您優化應用程序的查詢性能。
圖5:每秒和每分鐘的索引大小(MB)比較
這兩個集合中定義的索引的大小如圖5所示。MongoDB的最佳性能發生在索引和最近使用的文檔適合由WiredTiger緩存分配的內存時(咱們稱之爲「工做集」)。在咱們的示例中,咱們在4周的時間內只生成了5只股票的數據。對於這個小測試用例,咱們的數據已經爲PerSecond場景生成了一個大小爲103MB的索引。請記住,有一些優化(如索引前綴壓縮)能夠幫助減小索引的內存佔用。然而,即便有了這些優化,正確的模式設計對於防止失控的索引大小也是很重要的。考慮到增加軌跡,對應用程序需求的任何更改,好比在咱們的樣例場景中跟蹤超過5只股票或超過4周的價格,都將給內存帶來更大的壓力,最終須要將索引頁出到磁盤。當這種狀況發生時,您的性能將會降低。要緩解這種狀況,能夠考慮水平伸縮。
隨着數據大小的增加,當MongoDB副本集中託管主mongod的服務器達到物理限制時,可能最終會水平伸縮。
經過MongoDB Sharding水平擴展,性能能夠獲得提升,由於索引和數據將分佈在多個MongoDB節點上。查詢再也不針對特定的主節點。相反,它們是由一種稱爲查詢路由器(mongos)的中間服務處理的,該服務將查詢發送到包含知足查詢的數據的特定節點。注意,這對應用程序是徹底透明的——MongoDB爲你處理全部的路由
在比較前面的場景時,最重要的一點是使用數據包具備顯著的優點。場景2中描述的基於時間的bucketing將一分鐘的數據存儲到一個單一的文檔中。在物聯網等基於時間的應用中,傳感器數據可能以不規則的間隔生成,一些傳感器可能比其餘傳感器提供更多的數據。在這些場景中,基於時間的bucketing可能不是方案設計的最佳方法。另外一種策略是基於大小的bucketing。使用基於大小的bucketing,咱們將圍繞每一個發出的傳感器事件的特定數目的一個文檔來設計模式,或圍繞一成天(以先發出的爲準)來設計模式。
要了解基於大小的bucketing的實際運做,請考慮這樣一個場景:您正在存儲傳感器數據,並將bucket大小限制爲每一個文檔200個事件,或一天(以先到的方式)。注意:200的限制是一個任意的數字,能夠根據須要進行更改,而不須要更改應用程序或模式遷移。
{
_id: ObjectId(),
deviceid: 1234,
sensorid: 3,
nsamples: 5,
day: ISODate("2018-08-29"),
first:1535530412,
last: 1535530432,
samples : [
{ val: 50, time: 1535530412},
{ val: 55, time : 1535530415},
{ val: 56, time: 1535530420},
{ val: 55, time : 1535530430},
{ val: 56, time: 1535530432}
]
}
圖6顯示了一個基於大小的桶示例。在這種設計中,試圖將每一個文檔的插入限制爲任意數量或特定的時間段可能會很困難;可是,使用upsert很容易作到,以下面的代碼示例所示:
sample = {val: 59, time: 1535530450}
day = ISODate("2018-08-29")
db.iot.updateOne({deviceid: 1234, sensorid: 3, nsamples: {$lt: 200}, day: day},
{
$push: { samples: sample},
$min: { first: sample.time},
$max: { last: sample.time},
$inc: { nsamples: 1}
},
{ upsert: true })
當新的傳感器數據進來時,它被簡單地附加到文檔中,直到樣本數量達到200,而後因爲咱們的upsert:true子句建立一個新文檔。
這個場景中的最佳索引是{deviceid:1,sensorid:1,day:1,nsamples:1}。當咱們更新數據時,日期是精確匹配的,這是超級高效的。在查詢時,咱們能夠在單個字段上指定日期或日期範圍,這也頗有效,還可使用UNIX時間戳按第一個和最後一個進行過濾。注意,咱們使用整數值來表示時間。這些時間其實是做爲UNIX時間戳存儲的,只佔用32位存儲空間,而ISODate佔用64位存儲空間。雖然與ISODate相比查詢性能差異不大,但若是您計劃最終攝入tb級的數據,而且不須要存儲小於1秒的粒度,那麼將數據存儲爲UNIX時間戳可能很重要。
固定大小的Bucketing數據將產生很是類似的數據庫存儲和索引的改善,就像場景2中每次Bucketing所看到的那樣。這是在MongoDB中存儲稀疏物聯網數據的最有效方法之一。
咱們應該永久存儲全部數據嗎?超過必定時間的數據對您的組織有用嗎?舊數據的可訪問性如何?當您須要它時,它是否能夠簡單地從備份中恢復,或者它是否須要在線並做爲活動存檔供用戶實時訪問,以便進行歷史分析?正如咱們在本博客系列的第1部分中所述,這些是在上線以前應該問的一些問題。
有多種處理舊數據的方法,根據您的特定需求,有些方法可能比其餘方法更適用。選擇一個最適合您的需求。
對於幾年前生成的每一個事件,您的應用程序真的須要一個數據點嗎?在大多數狀況下,保持這種數據粒度的資源成本超過了可以隨時查詢到這個級別的好處。在大多數狀況下,能夠預先聚合和存儲數據,以便進行快速查詢。在咱們的股票示例中,咱們可能但願僅將天天的收盤價存儲爲一個值。在大多數架構中,預先聚合的值存儲在一個單獨的集合中,由於對歷史數據的查詢一般與實時查詢不一樣。一般對於歷史數據,查詢查找的是一段時間內的趨勢,而不是單個實時事件。經過將這些數據存儲在不一樣的集合中,您能夠經過建立更高效的索引(而不是在實時數據上建立更多索引)來提升性能。
在對數據進行歸檔時,與數據檢索相關的SLA是什麼?恢復數據備份是能夠接受的嗎?仍是須要數據處於在線狀態並隨時準備進行查詢?回答這些問題將有助於推進您的檔案設計。若是不須要對歸檔數據進行實時訪問,那麼能夠考慮備份數據並將其從活動數據庫中刪除。生產數據庫可使用MongoDB Ops Manager進行備份,若是使用MongoDB Atlas服務,則可使用徹底託管的備份解決方案。
一旦數據經過數據庫備份或ETL過程複製到存檔庫,數據能夠經過remove語句從MongoDB集合中刪除,以下所示:
db.StockDocPerSecond.remove ( { "d" : { $lt: ISODate( "2018-03-01" ) } } )
在本例中,「d」字段上定義的日期在2018年3月1日以前的全部文檔都將從StockDocPerSecond集合中刪除。
您可能須要設置一個自動化腳本,以便常常運行以清除這些記錄。或者,您能夠經過定義一個生存時間(TTL)索引來避免在這個場景中建立自動化腳本。
TTL索引與常規索引相似,只是定義了從集合中自動刪除文檔的時間間隔。在咱們的示例中,咱們能夠建立一個自動刪除超過一週的數據的TTL索引。
db.StockDocPerSecond.createIndex( { "d": 1 }, { expireAfterSeconds: 604800 } )
儘管TTL索引很方便,但請記住,檢查大約每分鐘發生一次,不能配置間隔。若是您須要更多的控制以使刪除不會在一天的特定時間發生,那麼您可能但願調度一個執行刪除操做的批處理做業,而不是使用TTL索引。
須要注意的是,使用remove命令或TTL索引將致使磁盤I/O偏高。對於已經處於高負載的數據庫,這多是不可取的。從活動數據庫中刪除記錄的最有效和最快的方法是刪除集合。若是能夠將應用程序設計爲每一個集合表明一段時間,那麼當須要歸檔或刪除數據時,只需刪除集合。這可能須要在您的應用程序代碼中使用一些智能來知道應該查詢哪些集合,但其好處可能大於此更改。當你發出一個刪除命令時,MongoDB也必須從全部受影響的索引中刪除數據,這可能須要一段時間,這取決於數據和索引的大小。
若是仍然須要實時訪問歸檔數據,請考慮這些查詢發生的頻率,以及只存儲預先聚合的結果是否足夠。
歸檔數據並保持數據可實時訪問的一種策略是使用分區分片對數據進行分區。切分不只有助於跨多個節點橫向擴展數據,還能夠標記切分範圍,以便將數據分區固定到特定的切分上。一種節省成本的措施是讓歸檔數據駐留在運行低成本磁盤的碎片上,並按期調整碎片自己中定義的時間範圍。這些範圍將致使平衡器自動在這些存儲層之間移動數據,爲您提供分層的、多溫度的存儲。查看使用分區分片建立分層存儲模式的教程,瞭解更多信息。
若是您的存檔數據不常常被訪問,而且查詢性能不須要知足任何嚴格的延遲sla,考慮備份數據並使用MongoDB Atlas或MongoDB OpsManager的可查詢備份特性。可查詢備份容許您鏈接到備份並向備份自己發出只讀命令,而無需首先還原備份。
MongoDB是一種廉價的解決方案,不只適用於長期歸檔,也適用於您的數據湖。投資Apache Spark等技術的公司能夠利用MongoDB Spark鏈接器。這個鏈接器將MongoDB數據物化爲與Spark和機器學習、圖形、流和SQL api一塊兒使用的數據數據庫和數據集。
一旦應用程序在生產環境中運行並具備多個tb的大小,從資源的角度來看,任何重大更改均可能很是昂貴。考慮這樣一個場景:您有6 TB的物聯網傳感器數據,而且以每秒50,000次插入的速度積累新數據。讀取的性能開始成爲一個問題,您意識到沒有正確地擴展數據庫。除非你願意讓應用程序宕機,不然這種配置模式的改變——例如從原始數據存儲轉移到bucket存儲——可能須要構建墊片、臨時暫存區域和各類臨時解決方案來將應用程序轉移到新的模式。這個故事的寓意是爲增加作計劃,並適當設計適合應用程序sla和需求的最佳時間序列模式。
本文分析了股票價格時序數據存儲的兩種不一樣模式設計。在您的場景中,最終獲勝的股票價格數據庫模式是最好的模式嗎?也許吧。因爲時間序列數據的性質和典型的數據快速攝入,答案實際上多是利用以讀或寫重用例爲目標的集合組合。好消息是,MongoDB靈活的模式很容易進行更改。實際上,您能夠運行兩個不一樣版本的應用程序,爲同一個集合編寫兩個不一樣的模式。可是,不要等到查詢性能開始降低時纔去尋找最佳設計,由於將現有文檔的TBs遷移到一個新的模式會花費時間和資源,而且會延遲應用程序的將來版本。在進行最終設計以前,您應該進行真實世界的測試。引用一句著名的諺語:「量兩次,切一次。」
在下一篇博客文章「用MongoDB查詢、分析和顯示時間序列數據」中,咱們將研究如何有效地從存儲在MongoDB中的時間序列數據中獲取價值。
關鍵提示:
MMAPV1存儲引擎不同意使用,所以使用默認的WiredTiger存儲引擎。請注意,若是您閱讀幾年前的模式設計最佳實踐,那麼它們一般是在舊的MMAPV1技術上構建的。
瞭解時間序列應用程序的數據訪問需求。
模式設計影響資源。關於模式設計和索引,「測量兩次,削減一次」。
若是可能的話,用真實的數據和真實的應用程序測試模式。
Bucketing數據減小了索引大小,所以大大減小了硬件需求。
時間序列應用程序一般會捕獲大量數據,所以只在對應用程序的查詢模式有用的地方建立索引。
考慮多個集合:一個集中在寫重插入和最近的數據查詢上,另外一個集中在預聚合數據的歷史查詢上的bucket數據集合。
當索引的大小超過託管MongoDB的服務器的內存量時,考慮橫向擴展,將索引和負載分散到多個服務器上。
肯定數據在何時過時,以及採起什麼操做,好比歸檔或刪除。
架構師亨哥韓
資產的生命週期 #資產管理#,#資產生命週期#,#ALM#,#物聯網#,#IoT#
視頻號