名詞解釋Glossary
bucket:帶有相同的元數據且在一段有限制的間 隔區間內的測量值組。
bucket collection :用於存儲時序型集合的底層的分組桶的系統集合。複製、分片和索引都是在桶級別上完成的。
measurement:帶有特定時間序列的K-V集合。
meta-data:時序序列裏很少隨時間變化的K-V對,同時可以用於識別整個時序序列。
time-series:一段間隔內的一系列測量值。
time-series collection:一種表示可寫的非物化的視圖的集合類型,它允許存儲和查詢多個時間序列,每個序列可以有不同的元數據。
MongoDB 在5.0中支持了新的timeseries collection類型的選項,該類型用於存儲時序型數據。timeseries collection提供了一組用於插入和查詢測量值的簡單接口,同時底層實際的數據是存儲在以bucket形式的集合中。
在創建timeseries collection時,timeField字段是最小必備的配置項。metaField是另一個可選的、可被指定的元數據字段,它是用於在bucket中對測量值分組的依據。MongoDB通過提供expireAfterSeconds字段選項,也支持了對測量值的過期機制。
在mydb數據庫中有個以mytscoll 命名的timeseries collection,該集合在MongoDB內部的catelog(用於存儲集合或視圖的信息)裏是由一個視圖和一個系統集合組成的。
mydb.mytscoll 是個視圖,它在MongoDB底層是用bucket collection作爲包含特定屬性的原始集合實現的:
該視圖就是通過aggregation裏的$_internalUnpackBucket來實現展開bucket裏數據的。
該視圖是可寫的(僅支持插入)。同時每個被插入的文檔必須包含時間字段。
在查詢視圖時,它會隱式地展開底層在bucket collection中存儲的數據,然後返回原始的非bucket形式的文檔數據。
該系統集合的命名空間是mydb.system.buckets.mytscoll,它是用來存儲實際數據的。
每一個在bucket collection裏的文檔,都表示了一組區間間隔的時序型數據。
如果在創建timeseries collection時,定義了metaField元數據字段,那麼所有在bucket裏的測量值都會有這個通用的元數據字段。
除了時間範圍,bucket還限制了每個文檔數據的總條數以及測量值的大小。
Bucket Collection Schema
{
_id: <Object ID with time component equal to control.min.<time field>>,
control: {
// <Some statistics on the measurements such min/max values of data fields>
version: 1, // Version of bucket schema. Currently fixed at 1 since this is the
// first iteration of time-series collections.
min: {
<time field>: <time of first measurement in this bucket, rounded down based on granularity>,
<field0>: <minimum value of 'field0' across all measurements>,
<field1>: <maximum value of 'field1' across all measurements>,
...
},
max: {
<time field>: <time of last measurement in this bucket>,
<field0>: <maximum value of 'field0' across all measurements>,
<field1>: <maximum value of 'field1' across all measurements>,
...
},
closed: <bool> // Optional, signals the database that this document will not receive any
// additional measurements.
},
meta: <meta-data field (if specified at creation) value common to all measurements in this bucket>,
data: {
<time field>: {
'0', <time of first measurement>,
'1', <time of second measurement>,
...
'<n-1>': <time of n-th measurement>,
},
<field0>: {
'0', <value of 'field0' in first measurement>,
'1', <value of 'field0' in first measurement>,
...
},
<field1>: {
'0', <value of 'field1' in first measurement>,
'1', <value of 'field1' in first measurement>,
...
},
...
}
}
索引indexes
爲了保證timeseries collection的查詢可以受益於索引掃描而不是全表掃描,timeseries collection允許索引可以被創建在時間上,元數據上以及元數據的子屬性上。從MongoDB5.2開始,在timeseries collection也允許索引被創建在測量值上。用戶使用createIndex命令提供的索引規範被轉換爲底層buckets collection的模式。
timeseries collection與底層的buckets collection之間的索引映射轉換關係細節,你可以參考timeseries_index_schema_conversion_functions.h.
在v5.2及以上版本的最新支持的索引類型,timeseries collection會存儲用戶原始的索引定義到變換後的索引定義上。當從底層的bucket collection的索引映射到timeseries collections的索引時,會返回用戶原始的索引定義。
當索引被創建後,可以通過listIndexes命令或$indexStats聚合計劃來檢查。listIndexes 和$indexStats是作用於timeseries collections的,執行時,它們會在內部將底層的bucket collection的索引轉化成timeseries格式的索引,並返回。比如,當我們在元數據字段中定義有mm的timeseries collection上執行listIndexes命令時,底層的bucket collection的{meta:1}索引,將會以{mm:1}格式返回。
dropIndex 和collMod (hidden:
時間字段上支持的索引類型:
單字段索引
組合索引
哈希索引
通配符索引
稀疏索引
多鍵索引
帶排序的索引
元數據字段和元數據子字段支持的索引類型:
支持所有時間字段上支持的索引類型
v5.2及以上版本支持2d 索引
v5.2及以上版本支持2dsphere 索引
v5.2及以上版本支持 Partial索引
僅在v5.2及以上版本,測量值字段支持的索引類型:
單字段索引
組合索引
2dsphere
部分條件索引
`timeseries collections 上不支持的索引類型,包括 唯一索引以及文本索引。
桶目錄Catalog
爲了保證高效地桶(分組)操作,我們在BucketCatalog裏維護了一組開啓的桶,你可以在bucket_catalog.h找到。在更高的級別,我們嘗試着把併發寫程序的寫操作分組合併爲可以一起提交地批處理,以減少對底層文檔的寫次數。寫程序會插入它的輸入批處理裏的每一個文檔到BucketCatalog,然後BucketCatalog會返回一個BucketCatalog::WriteBatch的處理器。一旦完成上面那些插入操作後,寫程序就會檢查每個寫批處理。如果沒有其他的寫程序已經對批處理聲明提交的權利,那麼它會聲明權利,並會提交它的批處理。否則,寫程序將會稍後再提交處理。當它檢查完所有的批處理,寫程序將會等待其他的寫程序提交每個剩下的批處理。
在內部,BucketCatalog維護一組對每個bucket 文檔的更新操作。當批處理被提交時,它會將這些插入轉換到成buckets的列格式,並確保任何control字段的更新(例如control.min 和 control.max)。
當bucket文檔在沒有通過BucketCatalog的情況下被更新時,寫程序就需要爲有問題的文檔或命名空間去調用BucketCatalog::clear ,這樣它就可以更新它的內部狀態,避免寫入任何可能破壞bucket 格式的數據。這通常由OP觀察者處理,但可能需要通過其他地方去調用。
bucket既可以通過手動設置選項control.closed 標識來關閉,也可以在許多場景下通過 BucketCatalog 自動關閉。如果BucketCatalog使用了超出給定的閾值(可通過服務器參數timeseriesIdleBucketExpiryMemoryUsageThreshold控制)的更多內存,此時它將會開始去關閉空閒的bucket。如果bucket是開啓的且它沒有任何未處於等待中未提交的測量值時,那麼它就會被視爲空閒的bucket。在下面這些場下 BucketCatalog 也會關閉bucket: 如果它擁有超過最大閾值(timeseriesBucketMaxCount)的測量值數據的數量;如果它擁有過大的數據量大小(timeseriesBucketMaxSize);又或者一個新的測量值數據是否是會導致bucket在其最舊的時間戳和最新的時間戳之間跨度比允許的間隔更長的時間(當前硬編碼爲一小時)。如果傳入的測量值在原理上與已經到達給定bucket的度量不兼容,該bucket將被關閉,同時可以使用numBucketsClosedDueToSchemaChange度量進行跟蹤。
在第一次提交給定bucket的寫批處理時,就會生成新的完整的文檔。後續的批處理提交中,我們只執行更新操作,不再生成新的完整的文檔(因此稱爲‘經典’更新),是直接創建DocDiff(“delta”或者v2的更新)。
粒度Granularity
timeseries collection的granularity 選項在集合創建的時候,可以被設置成seconds,minutes或者hours。後期可通過colMod操作來修改這個選項從seconds到minutes或者從minutes到hours,除此之外的轉化修改目前都是不支持的。該參數想要表示在已給定的時序型測量數據之間的粗略的時間間隔,同時也用於調節其他內部參數對分組的影響。
單個bucket被允許的最大時間跨度,是由granularity選項控制,對於seconds,最大的時間跨度被設置成1小時,對於minutes就是24小時,對於hours就是30天。
當通過BucketCatalog開啓新的bucket時,_id裏的時間戳就是等同於control.min.
更新和刪除
timeseries collection 支持符合以下限制的刪除語句:
僅支持metaField的屬性的查詢語句
支持批量操作
同時更新滿足上面同樣的條件,另外遵循:
僅支持metaField對應的屬性值
更新操作指定一個帶有更新運算符表達式的更新文檔(而不是替換文檔或者更新的pipeline操作)
不支持upsert:true 操作
這些更新與刪除的執行都會被轉換成相對應的底層的bucket collection的更新或刪除操作。特別是,對於查詢和更新文檔,我們會使用真正的字段meta 替換集合的metaField。(參見 Bucket 集合規範)
例如,對於一個使用 metaField: "tag"創建的timeseries集合db.ts,考慮一個對這個集合的更新操作,其查詢語句是{"tag.tag.a": "a"} ,同時更新文檔語句是 {$set: {"tag.tag.a": "A"}, $rename: {"tag.tag.b": "tag.tag.c"}}。這個更新操作在 db.system.buckets.ts上會被轉換成,查詢語句是{"meta.tag.a": "a"},更新語句是 {$set: {"meta.tag.a": "A"}, $rename: {"meta.tag.b": "meta.tag.c"}}。然後這個轉換後的更新語句就可以像普通的更新操作一樣執行。上面這些轉換流程也適用於刪除操作。
參考文獻References
MongoDB Blog: Time Series Data and MongoDB: Part 2 - Schema Design Best Practices
關於作者:黃璜
目前就職於上海DerbySoft,主要從事基礎架構中業務流程設計及研發的工作,平時工作中MongoDB使用的較多。
在提升自己外文的能力的同時,也希望爲社區做出微小的貢獻。
社區招募
爲了讓社區組委會成員和志願者朋友們靈活參與,同時我們爲想要深度參與社區建設的夥伴們開設了“招募通道”,如果您想要在社區裏面結交志同道合的技術夥伴,想要通過在社區沉澱有價值的乾貨內容,想要一個展示自己的舞臺,提升自身的技術影響力,即刻加入社區貢獻隊伍~ 點擊鏈接提交申請:
http://mongoingmongoing.mikecrm.com/CPDCj1B