時序數據庫 Apache-IoTDB 源碼解析之文件索引塊(五)

上一章聊到 TsFile 的文件組成,以及數據塊的詳細介紹。詳情請見:數據庫

時序數據庫 Apache-IoTDB 源碼解析之文件數據塊(四)微信

打一波廣告,歡迎你們訪問IoTDB 倉庫,求一波 Star 。歡迎關注頭條號:列炮緩開局,歡迎關注OSCHINA博客數據結構

這一章主要想聊聊:app

  1. TsFile索引塊的組成ui

  2. 索引塊的查詢過程編碼

  3. 索引塊目前在作的改進項spa

索引塊

索引塊由兩大部分組成,其寫入的方式是從左到右寫入,也就是從文件頭向文件尾寫入。但讀出的方式是先讀出TsFileMetaData 再讀出 TsDeviceMetaDataList 中的具體一部分。咱們按照讀取數據的順序介紹:.net

TsFileMetaData

TsFileMetaData屬於文件的 1 級索引,用來索引 Device 是否存在、在哪裏等信息,其中主要保存了:設計

  1. DeviceMetaDataIndexMap:Map結構,Key 是設備名,Value 是 TsDeviceMetaDataIndex ,保存了包含哪些 Device(邏輯概念上的一個集合一段時間內的數據,例如前幾章咱們講到的:張3、李4、王五)以及他們的開始時間及結束時間、在左側 TsDeviceMetaDataList 文件塊中的偏移量等。3d

  2. MeasurementSchemaMap:Map結構,Key 是測點的一個全路徑,Value 是 measurementSchema ,保存了包含的測點數據(邏輯概念上的某一類數據的集合,如體溫數據)的原信息,如:壓縮方式,數據類型,編碼方式等。

  3. 最後是一個布隆過濾器,快速檢測某一個 時間序列 是否是存在於文件內(這裏等聊到 server 模塊寫文件的策略時候再聊)。咱們知道這個過濾器的特色就是:沒有的必定沒有,但有的不必定有。爲了保證準確性和過濾器序列化後的大小均衡,這裏提供了一個 1% - 10% 錯誤率的可配置,當爲 1% 錯誤率時,保存 1 萬個測點信息,大概是 11.7 K。

咱們再回想 SQL :SELECT 體溫 FROM 王五 WHERE time = 1 。讀文件的過程就應該是:

  1. 先用布隆過濾器判斷文件內是否有王五的體溫列,若是沒有,查找下一個文件。

  2. 從 DeviceMetaDataIndexMap 中找到王五的 TsDeviceMetaDataIndex ,從而獲得了王五的 TsDeviceMetadata 的 offset,接下來就尋道至這個 offset 把王五的 TsDeviceMetadata 讀出來。

  3. MeasurementSchemaMap 不用關注,主要是給 Spark 使用的,ChunkHeader 中也保存了這些信息。

TsDeviceMetaDataList

TsDeviceMetaDataList 屬於文件的 2 級索引,用來索引具體的測點數據是否是存在、在哪裏等信息。其中主要保存了:

  1. ChunkGroupMetaData:ChunkGroup 的索引信息,主要包含了每一個 ChunkGroup 數據塊的起止位置以及包含的全部的測點元信息(ChunkMetaData)。

  2. ChunkMetaData :Chunk 的索引信息,主要包含了每一個設備的測點在文件中的起止位置、開始結束時間、數據類型和預聚合信息。

上面的例子中,從 TsFileMetadata 已經拿到了王五的 TsDeviceMetadataIndex,這裏就能夠直接讀出王五的 TsDeviceMetadata,而且遍歷裏邊的 ChunkGroupMetadata 中的 ChunkMetadata,找到體溫對應的全部的 ChunkMetadata。經過預聚合信息對時間過濾,判斷可否使用當前的 Chunk 或者可否直接使用預聚合信息直接返回數據(等介紹到 server 的查詢引擎時候細聊)。

若是不能直接返回,由於 ChunkMetaData 包含了這個 Chunk 對應的文件的偏移量,只須要使用 seek(offSet) 就會跳轉到數據塊,使用上一章介紹的讀取方法進行遍歷就完成了整個讀取。

預聚合信息(Statistics)

文中屢次提到了預聚合在這裏詳細介紹一下它的數據結構。

// 所屬文件塊的開始時間
private long startTime;
// 所屬文件塊的結束時間
private long endTime;
// 所屬文件塊的數據類型
private TSDataType tsDataType;
// 所屬文件塊的最小值
private int minValue;
// 所屬文件塊的最大值
private int maxValue;
// 所屬文件塊的第一個值
private int firstValue;
// 所屬文件塊的最後一個值
private int lastValue;
// 所屬文件塊的全部值的和
private double sumValue;

這個結構主要保存在 ChunkMetaData 和 PageHeader 中,這樣作的好處就是,你沒必要從硬盤中讀取具體的Page 或者 Chunk 的文件內容就能夠得到最終的結果,例如:SELECT SUM(體溫) FROM 王五 ,當定位到 ChunkMetaData 時,判斷可否直接使用這個 Statistics 信息(具體怎麼判斷,以後會在介紹 server 時具體介紹),若是能使用,那麼直接返回 sumValue。這樣返回的速度,不管存了多少數據,它的聚合結果響應時間簡直就是 1 毫秒之內。

樣例數據

咱們繼續使用上一章聊到的示例數據來展現。

時間戳 人名 體溫 心率
1580950800 王五 36.7 100
1580950911 王五 36.6 90

完整的文件信息以下:

POSITION|	CONTENT
-------- -------
0| [magic head] TsFile
6| [version number] 000002
// 數據塊開始
||||||||||||||||||||| [Chunk Group] of wangwu begins at pos 12, ends at pos 253, version:0, num of Chunks:2
12| [Chunk] of xinlv, numOfPoints:1, time range:[1580950800,1580950800], tsDataType:INT32,
[minValue:100,maxValue:100,firstValue:100,lastValue:100,sumValue:100.0]
| [marker] 1
| [ChunkHeader]
| 1 pages
121| [Chunk] of tiwen, numOfPoints:1, time range:[1580950800,1580950800], tsDataType:FLOAT,
[minValue:36.7,maxValue:36.7,firstValue:36.7,lastValue:36.7,sumValue:36.70000076293945]
| [marker] 1
| [ChunkHeader]
| 1 pages
230| [Chunk Group Footer]
| [marker] 0
| [deviceID] wangwu
| [dataSize] 218
| [num of chunks] 2
||||||||||||||||||||| [Chunk Group] of wangwu ends
// 索引塊開始
253| [marker] 2
254| [TsDeviceMetadata] of wangwu, startTime:1580950800, endTime:1580950800
| [startTime] 1580950800
| [endTime] 1580950800
| [ChunkGroupMetaData] of wangwu, startOffset12, endOffset253, version:0, numberOfChunks:2
| [ChunkMetaData] of xinlv, startTime:1580950800, endTime:1580950800, offsetOfChunkHeader:12, dataType:INT32, statistics:[minValue:100,maxValue:100,firstValue:100,lastValue:100,sumValue:100.0]
| [ChunkMetaData] of tiwen, startTime:1580950800, endTime:1580950800, offsetOfChunkHeader:121, dataType:FLOAT, statistics:[minValue:36.7,maxValue:36.7,firstValue:36.7,lastValue:36.7,sumValue:36.70000076293945]
446| [TsFileMetaData]
| [num of devices] 1
| [TsDeviceMetadataIndex] of wangwu, startTime:1580950800, endTime:1580950800, offSet:254, len:192
| [num of measurements] 2
| 2 key&measurementSchema
| [createBy isNotNull] false
| [totalChunkNum] 2
| [invalidChunkNum] 0
//布隆過濾器
| [bloom filter bit vector byte array length] 30
| [bloom filter bit vector byte array]
| [bloom filter number of bits] 256
| [bloom filter number of hash functions] 5
599| [TsFileMetaDataSize] 153
603| [magic tail] TsFile
609| END of TsFile

當執行: SELECT 體溫 FROM 王五 時:

  1. 從 599 開始讀,1 級索引長度爲 153.

  2. 599 - 153 = 446 就是 1 級索引讀開始位置,並讀出 TsDeviceMetadataIndex of 王五,其中記錄了,王五設備的 2 級索引的 offset 爲 254.

  3. 跳到 254 開始讀 2 級索引,找到 ChunkMetaData of 體溫, 其中記錄了體溫數據的 Chunk 的offset 爲 121

  4. 跳到 121 ,這裏進入了數據塊,從 121 讀取到 230 ,讀出的數據就所有是體溫數據。

改進項

1. 只讀投影列

前面第 3 步中,讀取 2 級索引時候,會將這個設備下的全部測點數據所有讀出來,這依然不太符合只讀投影列的設計,因此在新的 TsFile 中,修改了 1級索引和 2 級索引的部分結構,使得讀出的數據更少,更高效。有興趣的同窗能夠關注 PR: Refactor TsFile #736

2. 文件級 Statistics

在物聯網場景中常常會涉及到查詢某個設備的最後狀態,好比:車聯網中,查詢車輛的末次位置( SELECT LAST(lat,lon) FROM VechicleID ),或者當前的點火、熄火狀態等 SELECT LAST(accStatus) FROM VechicleID 

或者當某些分頁查詢等狀況時候,常常會使用到 COUNT(*) 等操做,這些都很是符合 Statistics 結構,這些場景涉及到的索引設計也都會體現到新的 TsFile 索引改動中。

到此已經介紹完了文件的總體結構,瞭解了大致的寫入和讀取過程,可是 TsFile 的 API 是如何設計的,怎樣在代碼裏作一些特殊的功課,來繞過 Java 裝箱、GC 等問題呢?歡迎持續關注。。。。


本文分享自微信公衆號 - 數據庫技術研究(atoildw)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索