在(zai)時(shi)(shi)序(xu)數(shu)據(ju)(ju)(ju)庫(ku)(ku)場景下,亂序(xu)數(shu)據(ju)(ju)(ju)的(de)定義為:“時(shi)(shi)間戳(chuo)(timestamp)不按照遞增(zeng)順序(xu)到達數(shu)據(ju)(ju)(ju)庫(ku)(ku)的(de)數(shu)據(ju)(ju)(ju)。”雖然它的(de)定義很簡單,但時(shi)(shi)序(xu)數(shu)據(ju)(ju)(ju)庫(ku)(ku)需要(yao)有相應的(de)處理(li)邏輯來保證數(shu)據(ju)(ju)(ju)存儲時(shi)(shi)的(de)順序(xu)性(xing),這勢必會增(zeng)加數(shu)據(ju)(ju)(ju)庫(ku)(ku)架構(gou)的(de)復雜性(xing),從(cong)而影響數(shu)據(ju)(ju)(ju)庫(ku)(ku)的(de)性(xing)能(neng)表現。
已知完全亂序的數據處理是業界的難題,因此 TDengine 需要重點解決的問題應該是——從實際的業務場景出發,如何對偶發亂序數據的進行高效處理(比如設備損壞斷電,網絡異常,數據補錄等問題)。
TDengine 的數據流向是:硬(ying)盤(WAL)→ 內存(Vnode Buffer)→ 硬(ying)盤(Data File)。
在 WAL 中,我(wo)們記錄(lu)的(de)是(shi)數據到達數據庫(ku)的(de)順序,但是(shi)數據入庫(ku)后,是(shi)一定要(yao)保證(zheng)時間戳的(de)有序的(de)。因此,亂(luan)序數據的(de)處理(li)發生在寫入 WAL 后的(de)兩個階段(duan):
基于以上(shang)邏輯,我(wo)們對亂序(xu)數據分(fen)為兩類:
一(yi). 內(nei)(nei)存(cun)亂序數據(ju):在(zai)同(tong)一(yi)張表的范疇內(nei)(nei),指時(shi)間戳(chuo)與內(nei)(nei)存(cun)數據(ju)的時(shi)間范圍相(xiang)交的數據(ju)。
二. 硬盤(pan)(pan)亂序(xu)數據:在同(tong)一(yi)張(zhang)表的(de)范疇內,指(zhi)時間戳與硬盤(pan)(pan)數據的(de)時間范圍相交的(de)數據。
對于類型一的亂序數據,TDengine 會在內存中通過為每張表建立一個跳表結構做好排序,從而解決問題。
場景: 創(chuang)建某表后,我(wo)們寫入(ru)了從(cong) 1970 年到 2023 年的一(yi)(yi)小(xiao)批(pi)數(shu)據(由(you)于(yu)(yu)數(shu)據量較少不足以觸發落盤)。當我(wo)們再寫入(ru)一(yi)(yi)條 1998 年時間戳的亂序(xu)數(shu)據時,會由(you)跳表進行排序(xu),排序(xu)后數(shu)據在內存中(zhong)以“1970-1998-2023”的順序(xu)有(you)序(xu)存在。該(gai)排序(xu)操作(zuo)的成本由(you)寫入(ru)操作(zuo)承擔,但由(you)于(yu)(yu)內存中(zhong)保留的只是極少數(shu)數(shu)據,因此影響極小(xiao)。
當來到落盤階段時(落盤的細節可參考:文章:與 TDengine 性能直接相關——3.0 的落盤機制優化及使用原則),這三條有序的(de)數(shu)據(ju),又都有可能成(cheng)為亂(luan)序數(shu)據(ju)的(de)存在(zai)——即類型二(er):
首先,我們交代一下硬盤上數據文件的設計背景:由于 TDengine 通過建庫參數 duration(days) 做數據分區,假設某庫 duration 設置為 10 日,那么自打 1970 年 1 月 1 日 0 時起,每隔 10 天就會劃分一個數據文件組。寫入的數據時間戳歸屬于哪個時間范圍,便會寫入哪個數據文件組中——即每個數據文件組中都包含了固定時間范圍內的數據。而這些數據以數據塊的形式存在,每個數據塊所包含的時間范圍視落盤時實際情況而定。
落盤時,數據(ju)會(hui)各自找(zhao)到自己(ji)所屬的(de)數據(ju)文件組(zu)(zu),根據(ju)該數據(ju)文件組(zu)(zu)中已有(you)的(de)數據(ju)塊的(de)時間范(fan)圍計算,來判斷(duan)數據(ju)的(de)亂序與否。由此,會(hui)衍生出(chu)各種(zhong)不同的(de)情(qing)況(kuang)。我們選出(chu)屬于(yu) 2023、1998、1970 年份(fen)的(de)三條數據(ju),分(fen)別舉例三種(zhong)情(qing)況(kuang):
- “2023-01-01 xxxxxxx” 這條數據落盤時,并沒有和這個表的任何數據塊的產生時間交集——也就是非亂序的正常數據。
- “1998-01-01 xxxxxxxx”:這條數據落盤時雖然和該表的整體時間范圍(1970–2023)產生交集,但是局部時間范圍內沒有和該表任何數據塊產生交集。這種亂序通常發生于歷史數據的數據補錄。比如:被采集的設備斷電很久后恢復使用。因此這條數據雖然看似亂序,但實際上和正常數據差別不大。
- “1970-01-01 xxxxxxxx” ,這條數據在落盤時和該表數據塊的時間范圍產生了交集:早在 2.0 當中,如果落盤時的數據和已有數據塊時間戳相交的時候,亂序數據會形成一個子塊追加在數據文件中,查詢時需要把子塊的數據讀到內存中再做排序,當子塊比較多的時候就會影響查詢性能——經過重新設計后,在 3.0 當中亂序數據和原有數據將會合并重寫為新的數據塊,以追加的方式寫入數據文件中并且重寫索引,而舊的數據塊則被視為碎片文件。這樣一來,處理數據的成本就被轉嫁給了落盤操作,因此對后續的查詢基本沒有影響。
綜(zong)上,在亂(luan)序數(shu)(shu)據(ju)寫入硬盤的(de)時(shi)候,由于(yu)數(shu)(shu)據(ju)塊和索引文件均(jun)是(shi)(shi)新生成(cheng)的(de),因此它對(dui)于(yu)后(hou)續的(de)查詢是(shi)(shi)比較(jiao)友好的(de)。考慮到(dao)亂(luan)序數(shu)(shu)據(ju)一定是(shi)(shi)業務上的(de)偶發場景,因此這樣處理基本不會(hui)造成(cheng)性能負擔(dan)。即便是(shi)(shi)產生了少部分(fen)由于(yu)亂(luan)序帶(dai)來的(de)碎片數(shu)(shu)據(ju)、無效數(shu)(shu)據(ju)塊也都可(ke)以(yi)由企業版(ban)功能 compact 清除或者重組。
從很多角度來說,TDengine 3.0 都已經達成了長足的優化。因此隨著 2.0 時代逐步進入尾聲,我們也希望大家可以盡早從 2.0 切換到 3.0 之上:《如何把數據從 TDengine 2.x 遷移到 3.x ?》。


























