周騎駿,王 鵬,汪 衛
(復旦大學 計算機科學技術學院,上海 201203)
時間序列數據[1-2]是指隨著時間進行變化的一系列數據,通常存在于日志數據中,例如某天的CPU、硬盤使用率、某個地區一年的溫度等。這些數據由物聯網中的傳感器進行采集,并上傳至服務器作為每天的日志數據。數據科學家將其作為基礎數據,查詢并分析該數據從而得出結果來進一步優化目標。時間序列數據具有數據格式單一、數據種類復雜、數據之間關聯性強等特點。關于時間序列的查詢通常需要構建索引,索引構建方法主要有B+樹[3-4]、布隆過濾器[5]、solr搜索服務器[6]。對于時間序列而言,大部分時間上相鄰的時間序列數據具有較強的關聯性,單條數據的索引不適用于時間序列數據。文獻[7]在無序的基于LSM樹[8-9]數據存儲上對觀測的時間序列數據進行存儲索引,將時間序列數據按照時間劃分成一塊塊固定長度的分段并且進行查詢。該方法可以使用較小的代價就能完成對時間序列數據的快速訪問,但由于其是固定分段,因此沒有考慮時間序列本身的特征,缺乏靈活性,在進行范圍查詢時,獲取的無關數據量較多,查詢效率較低。
本文提出一種基于動態分段的時間序列索引DSI。該索引將一條時間序列根據自身數據特點,設置相應的限制參數進行分段,形成數據長度不同的分段。對于分段查詢,使用區間樹對分段塊進行搜索并通過主鍵查詢數據。
時間序列T是采集到的時間長度為m的數據,可表示為T={t1,t2,…,tm-1,tm},按照一定的時間間隔采集時間序列中的數據t1、t2,相鄰點的時間差值相等。
時間序列數據存儲一般是以采集數據的時間作為主鍵,依據采集時間進行排序,相鄰數據具有一定的關聯性。因為使用者讀取某一時間數據后,很有可能繼續讀取相鄰時間段的數據,所以讀取時間序列數據時不只是讀取一條數據,而是連續讀取一段時間序列數據,為創建分段索引提供了理論依據。
在數據查詢時,首先向索引查詢框架(如圖1所示)輸入請求,索引查詢框架根據請求確定目標數據塊,然后返回目標索引塊的行鍵,最后用戶根據行鍵存儲系統中查詢的目標數據。

圖1 索引查詢框架
DSI是根據時間序列數據目標字段的值進行動態切分的一種索引。相對于將數據劃分為相同長度的數據塊索引來說,DSI根據數據值之間的關系進行切分及索引,能夠減少無效數據的讀取,提升讀取效率。在圖2中,兩條虛線之間的數據為一個數據段。每個數據段包含數據的個數不同,算法根據時間序列數據的特性,在數據點之間差異大、數據波動大的地方,例如一條時間序列的波峰和波谷區間,減少相應的段長度。對于數據波動較少的地方增加相應的段長度,同時對于數據頻度較小,即數據出現次數較小的數據段減少相應的數據段長度,從而提升有效數據的讀取量。

圖2 索引數據塊的動態劃分
2.3.1 DSI結構
DSI索引主要包含索引塊和區間樹兩部分。由于時間序列的數據值隨時間的變化而變化,因此根據時間將存儲在存儲系統中的每個時間序列段劃分為一個個更小的數據段。
R={[r1,r2),[r2,r3),…,[rm-1,rm)}
(1)
其中,R為一個索引塊集合,rm表示時間序列中第m個點的主鍵,[rm-1,rm)定義為左閉右開的區間。當用戶進行范圍查詢時,給出一個查詢區間。將查詢區間和索引序列段上信息進行對比,如果符合條件,則表明時間序列數據段是用戶所需的數據,因此提取整個數據塊。圖3為索引整體結構。在存儲系統中,根據主鍵查詢存儲系統中的數據,將時間序列根據其自身特點劃分為一個個邏輯數據塊。在索引層中,為每一個邏輯數據塊創建一個相應的索引塊,該塊包含了描述邏輯數據塊的信息,然后使用區間樹獲取索引塊。

圖3 索引整體結構
2.3.2 索引塊的描述
一條索引塊表示的數據塊可能包含成百上千條數據,也可能包含幾十條數據,根據時間序列數據的特點確定,因此需找到一個合適的方法對這些數據進行切分。對于每一個目標數據塊,把目標塊中的最大值和最小值作為索引的關鍵信息。這樣既能節省索引空間,又能在一定程度上保持查詢準確率。因此,對于每一個索引塊,可將其看作[min,max]區間。對于區間查詢來說,區間查詢的左右端點構成的區間與索引塊區間相交,該塊是需要查詢的數據塊。同時,由于描述信息簡單,因此當有大量數據插入時,索引建立速度也較快。表1詳細介紹了索引中各字段的數據構成,其中tolerance字段表示該索引代表的數據塊中數據值的差值,該差值用于下文中索引塊的生成。
2.3.3 索引塊的生成
根據上文描述可知,數據之間的差值越小,代表數據之間的波動越小。數據之間的差值越大,數據值之間的波動越大,則應該切分成不同的數據段。但對于每一類時間序列數據,由于數量之間的差異,因此對于不同的時間序列數據塊的切分不同,需要用戶指定差值E。同時,根據差值E確保每個數據段中的數據之間的差值小于差值E。
推論1對于一個普通的時間序列t[i:j],要保證時間序列t[i:j]不被切分,只需保證該時間序列數據的最大值和最小值的差值小于E即可,證明如下:
range[i:j]=maxt[k]-mint[k]≤E,i≤k≤j
(2)

綜上所述,確保每個數據段中數據之間的差值小于E,只需找到數據段的最大值和最小值進行切分。同時,借助于APCA[10-11]思想,動態劃分時間序列,基于此,本文提出一種不定長的時間序列分塊劃分算法。
算法1時間序列分塊劃分算法
輸入時間序列T,基準差值E>0,差值級別count
輸出索引集合indexList
1.indexList ← {}
2.初始化minValue,maxValue,lastTol
3.計算T的標準差和均值mean,stdv
4.map← splitTolerance(mean,E,stdv,count)
5.for each data t[i]in T do
6.tol← min(map.get(t[i]),lastTol)
7.If max(maxValue,t[i])-min(minValue,t[i])>tol
//生成索引塊并加入到索引集合中
8.indexBlock←generateIndexBlock()
9.indexList.add(indexBlock)
10.重置minValue,maxValue,lastTol
11.else
12.minValue←min(minValue,t[i])
13.maxValue←max(maxValue,t[i])
14.lastTol←tol
15.end if
16.end for
17.return indexList
由算法1可知,該算法的輸入為時間序列T,用戶的輸入差值E以及用戶劃分的差值級別count,用戶對于不同數值大小的數據切分差值也不同。輸出為切分的塊,即索引集合。首先初始化各參數值,其中,minValue代表當前段的最小值,maxValue代表當前段的最大值,lastTol代表上一個相鄰的索引(第1行、第2行)。然后計算出時間序列的數據均值mean以及標準差stdv,便于下文不同數值數據進行級別劃分賦予相應差值。在得到均值及標準差后,根據均值、標準差以及基準差值生成相應的差值項(第3行)。函數splitTolerance根據不同數據值產生不同的差值,由一個鍵值對形式的集合map進行保存。遍歷時間序列T中的每項值t[i],首先獲取當前t[i]在map中對應的差值,然后和上條數據的差值lastTol對比并取較小值。根據推論1,用當前段的最大值減去最小值確定是否超過當前差值,如果超過差值則進行索引塊的建立(第4行)。接著為下一個索引塊重置minValue、maxValue、lastTol(第5行~第10行)。反之,保留當前差值,即賦值給lastTol,同時將minValue、maxValue和t[i]比較更新最小值和最大值(第12行~第14行)。
splitTolerance是根據差值進行切分的函數,輸入參數為差值級別count、均值mean、標準差stdv及基準差值E。在現實數據中,存在異常數據及極端數據。拉伊達法則[12]表明均值mean范圍上下的3個標準差stdv的數據范圍內能夠涵蓋接近99%的數據,同時數據數值和mean值越接近,數據頻度越大。圖4為一個count值為2的區間劃分結果。首先設立一個以mean值為中心,范圍為[mean-3stdv,mean+3stdv]的區間。count等于2代表將[mean,mean+3stdv]平均分成兩份,從而產生2個區間[mean,A]和[A,mean+3stdv],同一區間內的數據差值一樣。距離中心值越近的區間,該區間的差值越大,區間[mean,A]和[A,mean+3stdv]產生了2個差值K1,K2。[mean,A]區間距離mean值更近,因此差值K1大于K2。K1值為基準差值E,K2值為E/2。對于[mean+3tdv,max]特殊區間中點差值,則使用相鄰區間的差值,在本例中為K2的值。同理,如果count為n,則在n個切分區間中的第i(K1≤i≤n)個區間差值數據為E/i,[mean+3tdv,max]的區間數據為E/n。因為mean值左邊部分與右邊部分是對稱的,所以不再贅述。

圖4 數據差值判斷
2.4.1 區間樹結構
本文主要利用區間樹進行索引塊的快速查找,區間樹是由動態集合維護的紅黑樹,是一種自平衡的二叉樹,主要用于對區間數據的查詢,區間樹由紅色節點和黑色節點構成,因此對n個節點區間的插入和刪除都能在O(lgn)時間內完成。區間樹的結構如表2所示。區間樹每個節點主要由五部分構成:左右端點left和right,left是一個實數值,right是一個鍵值對形式的集合;左右孩子區間樹節點指針node_L和node_R,分別指向左右孩子區間樹節點;NodeMax,代表當前節點及子樹節點的左右端點的最大值。在表2中,第2條數據id為2,左端點數據為8,右端點是一個鍵值對集合,包含有關鍵字為9及關鍵字為20的鍵值對元素,同時左子節點是id為3的節點,右子節點不存在。當前節點以及子樹的最大值NodeMax為20。

表2 區間樹結構
2.4.2 區間樹建立
在建立區間樹時,需先了解如何將一個索引塊節點轉化為一個區間樹節點。在查詢區間樹時,主要是通過比較區間樹節點的左端點與右端點進行查詢,因此當構造區間樹時,將獲得的索引塊的min值作為區間樹的左端點left,max值作為區間樹的右端點right的一部分。由于區間樹可能出現左右端點一致的情況,造成很多的冗余節點,因此將區間樹的右端點轉化為一個有序集合。需要注意的是,該有序集合是一個鍵值對形式的集合,鍵是每一個索引塊的max值,而值是一個鏈表形式的集合list,list中存儲的是索引塊的rowkey_start起始主鍵和rowkey_end結束主鍵等關鍵信息,因此左端點相同的數據都在同一個區間樹節點中。圖5為索引塊到區間樹節點的轉化表示,對于一個blockid為10的索引塊,索引塊的min值為8,max值為20,將min值作為區間樹節點的左端點,max值作為區間樹節點的右端點,同時NodeMax值為max值。

圖5 索引塊到區間樹的轉化過程
相對于傳統將每一個索引塊都獨立為一個樹節點的方法,區間樹查詢將左端點相同的索引塊進行融合,根據NodeMax剪枝以及右端點有序的特點,減少查詢目標數據時的比較次數。在區間樹的建立過程中,輸入已劃分好的索引塊,每個索引塊都有一個min值和max值,只需將索引塊轉化為區間的節點數據即可。首先初始化一個區間樹,并獲取樹的根節點,然后初始化一個臨時節點。對于索引塊集合的每一索引塊,先將其轉化為區間樹節點。其中索引塊的max值為區間節點的NodeMax值,將根節點的NodeMax值和當前的NodeMax進行對比,取兩者較大的值來更新根節點的NodeMax值。接著使用當前插入節點和根節點的左端點進行比較,如果左端點小于根節點的左端點,則繼續和根節點的左子樹進行比較。如果2個左端點相等則將該節點加入當前節點的存儲集合中,并取2個節點的NodeMax值的較大值為新的NodeMax值,表示這2個節點共用一個樹節點;否則與根節點的右子樹節點進行比較。如果不存在left值相同的節點,則在葉子節點中創建一個新的區間節點。最后根據紅黑樹的性質進行樹旋轉操作來保持整個樹的平衡。旋轉操作的詳細過程可參見文獻[13-14]。
圖6為索引塊節點在區間樹中的插入過程,其中右半部分的區間樹根據表2構建而成。在每一個區間樹節點中,虛線上半部分代表了該節點的左右端點,虛線下半部分表示該節點的NodeMax值。將blockid為11的索引塊轉化為left為8、right鍵值對集合中鍵為17的一個區間樹節點,NodeMax的值為17。由于區間樹以左端點為關鍵字進行排序,因此插入節點的left值(8)和根節點left值(10)進行比較,由于插入節點的left值小于根節點left值,因此進一步遍歷根節點的左子節點。在遍歷左子節點時,發現有left為8的區間節點,便插入該節點中。由于右端點right是一個有序集合,其中key包含9和20等數值,在對右端點鍵為17的數據進行插入時,只需找到第一個大于17的數據插入到該數據中。在本例中,插入到鍵為20的元素前。

圖6 區間樹的插入過程
2.5.1 索引查詢算法
索引塊存在于區間樹節點中,先從區間樹中找出索引塊。對于區間樹的查詢[15-17],除了圖7中兩種區間樹查詢沒有命中的情況,即查詢區間的左端點大于區間節點的右端點和區間節點的左端點小于查詢節點的右端點,其余都是需要查詢的目標線段。在圖7中,細實線為查詢的目標區間,粗實線為區間樹中的區間。

圖7 區間樹查詢不命中的情況
在獲得目標區間后,遍歷區間樹中的每個節點,給出索引塊的查詢算法,其中輸入為區間樹根節點t、查詢區間[left,right]、區間集list。
算法2索引查詢算法RecursiveSearch
//區間樹左端點不大于查詢區間右端點兩者才有交集
1.if t !=NIL and t.leftpoint<=right
//返回區間樹右端點第一個大于查詢區間左端點的位置
2.start<-findFirst(t.rightpoint,left)
//將該位置開始一直到有序集合末尾數據都加入list
3.list.add(t.rightpoint,start)
4.end if
5.leftchild<-t.leftchild;rightchild<-t.rightchild
6.if leftchild!=NIL and
7.leftchild.NodeMax>=left
8.RecursiveSearch(leftchild,left,right,list)
9.end if
10.if rightchild!=NIL and
11.rightchild.NodeMax>=left
12.RecursiveSearch(rightchild,left,right,list)
13.end if
該算法是一個遞歸算法,輸入為區間樹根節點t,查詢區間[left,right]以及用于存放數據的集合list,主要是對區間樹中的節點進行遍歷,先判斷該節點是否存在圖7(a)的情況,即查詢的區間右端點right小于區間樹節點的左端點t.leftpoint。如果滿足該情況,則可以把當前節點排除(第1行)。對于圖7(b)的情況,因為區間樹右端點t.rightpoint是一個集合,則只要判斷區間樹的右端點集合t.rightpoint中是否有數據大于查詢區間的左端點left即可,因為區間樹的右端點集合t.rightpoint依次遞增,只需找到右端集合t.rightpoint中第一個大于查詢區間的左端點left即可。調用findFirst函數找到右端點集合元素key中第一個大于查詢區間左端點left,并返回鍵值對序號start(第2行)。由于區間樹的右端點t.rightpoint中的key集合是有序的,從start開始的位置一直到集合末尾右端點集合元素中的key都大于查詢區間左端點left,這些集合元素都滿足查詢要求,因此從start位置開始到集合末尾位置的數據都加入到list(第3行)。由于無需比較每一個節點集合中所有數據,因此在一定程度上提高了查詢效率。接著判斷左子節點leftchild的NodeMax值是否大于查詢區間左端點left,左子節點leftchild的NodeMax代表了當前左子樹的節點最大值。如果NodeMax不大于查詢區間左端點left,則滿足圖7(b)的情況,可進一步進行剪枝(第6行~第9行)。如果不滿足,則繼續以相同方式遍歷右子節點rightchild(第10行~第13行)。總體來說,通過多個索引段的融合排序以及NodeMax剪枝,可使整個區間樹查詢算法效率得到進一步提升。本質上該算法是對二叉樹的遍歷,二叉樹的遍歷時間復雜度為O(n),總的時間復雜度為O(n),n為區間節點個數。由于區間樹節點具有NodeMax屬性,同時區間樹節點的右端點是有序的,因此在進行查詢時一定程度上減少了查詢比較次數,最終的時間復雜度應遠小于O(n),空間復雜度為O(1)。
2.5.2 查詢結果優化
對于一個區間查詢請求,可能返回很多的索引段。因此需要進一步縮減結果,文獻[18-19]使用層次聚類算法將多個結果集合進行聚類進一步減少結果數。圖8展示了將查詢出的時間序列結果進行層次聚類的過程,候選集為4個聚類,需要聚合成2個聚類。左邊數字為時間序列塊,右邊數字為候選集結果聚類r。首先計算所有結果集中兩兩聚類之間的距離。找出聚類集中距離最小的2個聚類集r1、r2,距離最小代表2個聚類融合引入的無關數據量最少。將r1、r2進行融合變成r5,接著進一步融合,計算r3、r4、r5中距離最小的2個聚類,因為r3、r4融合需要引入序號為5、6的不相關時間序列數據段,而r5、r6融合需要僅引入序號為3的不相關時間序列數據段,代價較小,因此將r5、r3融合成r6。

圖8 層次聚類過程
算法3展示了層次聚類的具體過程,輸入數據為結果集合list,目標聚類數count,先將list中的每一項轉化為一個cluster(第1行),然后對cluster中的每兩項進行兩兩距離計算,由于是對區間進行層次聚合,因此選取的距離函數是找到2個聚類中的區間最小距離(第2行、第3行)。在每次合并后,就減少一個聚類個數,如果聚類個數達到要求則返回;否則繼續進行下一輪聚類(第4行)??傮w來說,本文方法是以犧牲一定的查詢精準度來獲取更少的結果集,其每次只保存最小距離值,因此空間復雜度為O(1),而需要計算出兩兩聚類的距離,時間復雜度為O(n3),n為當前聚類個數。
算法3層次聚類算法
輸入結果集list、聚類個數count
輸出目標聚類個數listclusters
1.將list中的每一項轉化成為一個cluster,得到cluster的集合listclusters
2.對listclusters中的每一項,兩兩計算距離
3.找到距離最小的2個聚類,合并聚類
4.判斷當前cluster個數是否小于count,若大于count則返回步驟2,若小于count則進行步驟5
5.返回結果集listclusters
實驗在單臺計算機上運行,處理器為Intel Core i7-4710 2.5 GHz,內存大小為24 GB,操作系統為64位Windows 7,JDK版本為1.8.0_152。
實驗數據主要來自于CMOP[20]觀測數據和股票數據2個數據集。CMOP觀測數據來自于傳感器測量出的溫度數據,數據時間范圍為2008年12月1日至2011年11月30日,數據量約為3 000萬,溫度數據平緩、波動較小。股票數據主要是來源于滬深股市300支股票的一檔掛單數據(通過電腦進行買賣的股票數據),數據時間范圍為2013年3月1日至2013年12月31日,數據量約為600萬,該數據相對溫度數據震蕩、波動較大。本文對比方法為基于固定分段的時間序列查詢索引CRI[7]。
3.3.1 相同量級數據集下的數據查詢效果
為保證實驗公平性,在索引塊數量一致的情況下進行相同數據的查詢。DSI和CRI的查詢效率如圖9所示。橫坐標為進行查詢的數據區間實際包含數據的條數,縱坐標為查詢到的結果集的數據量。在CMOP數據集中,對實際包含數據量為30、300、3 000的數據區間進行查詢,最終返回索引所標記的數據量。由于數據查詢是以塊的方式進行,因此查詢到的數據總量越少,代表查詢到的數據包含的無關數據量越少,查詢精度越高。由圖9可以看出,DSI效果優于CRI,且由于股票數據的波動較大,因此對此目標查詢區間,DSI能夠有效識別并劃分區間。

圖9 相同量級數據集下查詢效率對比
3.3.2 不同量級數據集下的數據查詢效果
在DSI和CRI產生的索引塊數量一致的情況下,圖10展示了不同數據總量下的查詢效率,在CMOP數據集和股票數據集中,查詢數據量是總數的10-4,對于總的數據量為3 000萬的數據集,查詢數據量為10-4,即查詢包含3 000條的數據區間,由此可知,查詢到的總數據量小的索引的無關數據少,效率高??梢钥闯?DSI仍優于CRI,主要原因在于DSI是根據目標查詢數據的分布來動態切分數據。在該過程中,DSI關注了數據分布,而CRI是靜態劃分,僅記錄最大值和最小值,因此在查詢時經常訪問到包含極端值的塊,從而導致查詢到的無關數據較多,數據量增加。對于DSI動態查詢,極端值根據差值被切分成不同的段,從而不影響正常的數據查詢。

圖10 不同量級數據集下的查詢效率對比
3.3.3 數據差值對查詢結果的影響
圖11為對3 000萬CMOP觀測數據、600萬股票數據的查詢結果。每一類數據都是調整不同差值后,對各自相同數據區間進行查詢。對于股票數據而言,當差值數據為20、40、60、80時,對應的數據總塊數分別為1.39×106、6.9×105、3.5×105、1.9×105。隨著差值的增加,數據總塊數逐漸減少,但查詢數據逐漸增多。隨著差值的減少,數據總塊數增加,在查詢目標塊數時需要花費更多時間,但查詢無關數據少。對于較平緩的CMOP觀測數據,當差值數據為0.1、0.5、1.1、1.5時,對應的數據總塊數分別為2.79×106、4.8×105、1.6×105、1.1×105。隨著數據總塊數的減少,查詢出的數據也相對減少。

圖11 數據差值對查詢結果的影響
3.3.4 差值等級對查詢結果的影響
圖12是不同差值等級對查詢結果的影響。對于CMOP感測數據而言,當差值等級為1、5、10、15時,對應的數據總塊數分別為2.5×104、2.6×105、7.5×105、1.09×106;對于股票數據而言,當差值等級為1、5、10、1.5時,對應的數據總塊數分別為3.8×105、8.7×105、1.43×106、1.65×106。由此可知,隨著差值等級值的變大,產生的索引塊數會變多。在差值逐漸變大的過程中,早期對提升數據查詢精準度有一定的影響,隨著差值增大到一定程度時,例如CMOP數據和股票數據差值在15時,索引塊數變多,但查詢精度并沒有明顯提升,因此選取一個合適的差值等級至關重要。

圖12 差值等級對查詢結果的影響
3.3.5 區間樹查詢效率實驗
表3是區間樹查詢的效率結果。第2行~第4行是溫度數據的區間查詢情況。對于第2行數據來說,生成總區間段是67 967個,由于利用紅黑樹節點的left值進行排序,相同left值會加入到同一節點,因此生成的節點數大幅減少,同時,每個樹的右邊節點是一個根據right值排好的鍵值對集合,在查詢時無需對區間樹節點right值集合中的每一個值進行判斷,只需找到樹集合中大于查詢區間的左端點的第一個值即可,后續節點不用判別是否相交,便可直接加入。同時,每個節點還有NodeMax值,通過NodeMax值算法能在一定程度上減少查詢時與區間樹節點的比較次數。在67 957個區間中,查詢180個區間,只需與區間樹節點進行13 523次比較。對于股票數據,由于數據波動大但范圍小,因此生成的節點數比較少。由以上結果可知,區間樹查詢在一定程度減少了區間遍歷的次數。

表3 區間樹查詢效率
3.3.6 層次聚類算法對查詢結果的影響
圖13為層次聚類算法對查詢結果的影響,顯示了3 000萬CMOP數據集和600萬股票數據集對于10 000條數據及200條數據的查詢情況。由此可知,隨著聚類數的增加,查詢結果集的冗余數逐漸變少,但對于用戶來說,結果集變多,將耗費更多的時間去查看其中結果,因此用戶需根據不同數據的特點選擇合適的結果集。

圖13 層次聚類算法對查詢結果的影響
本文針對時間數據的局部性特點,提出基于動態分段的時間序列值查詢索引,用戶根據數據特點通過動態分段提升查詢效果,并使用層次聚類算法解決結果集過多的問題。實驗結果表明,本文索引的查詢效率優于傳統時間序列索引。但層次聚類需要用戶手動設置,因此下一步可將層次聚類的參數改為自動設置,減少查詢冗余,同時根據不同時間序列數據的特點調整輔助參數,進一步提升查詢性能。