技術乾貨| MongoDB時間序列集合

語言: CN / TW / HK

名詞解釋 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 格式的索引,並返回。比如,當我們在元資料欄位中定義有 mmtimeseries collection 上執行 listIndexes 命令時,底層的 bucket collection{meta:1} 索引,將會以 {mm:1} 格式返回。

dropIndexcollMod (hidden: <bool>, expireAfterSeconds: <num>) 也同樣支援在 timeseries collection 上。

時間欄位上支援的索引型別:

元資料欄位和元資料子欄位支援的索引型別:

  • 支援所有時間欄位上支援的索引型別
  • v5.2及以上版本支援2d 索引
  • v5.2及以上版本支援2dsphere 索引
  • v5.2及以上版本支援  Partial索引

僅在v5.2及以上版本,測量值欄位支援的索引型別:

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 collectiongranularity 選項在集合建立的時候,可以被設定成 secondsminutes 或者 hours 。後期可通過 colMod 操作來修改這個選項從 secondsminutes 或者從 minuteshours ,除此之外的轉化修改目前都是不支援的。該引數想要表示在已給定的時序型測量資料之間的粗略的時間間隔,同時也用於調節其他內部引數對分組的影響。

單個 bucket 被允許的最大時間跨度,是由 granularity 選項控制,對於 seconds ,最大的時間跨度被設定成1小時,對於 minutes 就是24小時,對於 hours 就是30天。

當通過 BucketCatalog 開啟新的 bucket 時, _id 裡的時間戳就是等同於 control.min.<time field> 的值,該值是從第一個插入 bucket 的測量資料中根據 granularity 選項來向下近似舍入而得到的。對於 seconds ,它將向下舍入到最接近的分鐘,對於 minutes ,將向下舍入到最接近的小時,對於 hours ,它將向下舍入到最接近的日期。在閏秒和日曆中的其他不規則情況下,這種舍入可能並不完美,並且通常通過對自紀元以來的秒數進行基本模運算來完成,假設每分鐘 60 秒,每小時 60 分鐘,以及每天 24 小時。

更新和刪除

timeseries collection 支援符合以下限制的刪除語句:

metaField

同時更新滿足上面同樣的條件,另外遵循:

metaField
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