IndexR是由舜飛科技研發的實時OLAP系統。於 2017 年 1 月初正式開源,目前已經更新至 0.6.1 版本,其作者認為IndexR具有以下特點:
- 超大數據集,低查詢延時(超大數據集由HDFS保證,查詢低延遲由MPP架構的Drill和IndexR專門設計的存儲格式保證)
- 准實時 (和Druid實時攝入的思路類似,從Kafka實時攝入數據)
- 高可用,易擴展(架構設計簡單,只有一種節點,可以輕易橫向擴展)
- 易維護(支持Schema在線更新)
- SQL支持 (由Drill支持,實際上Drill也是利用Calcite實現的)
- 與Hadoop生態整合(Hive,Kafka,Spark, Zookeeper, HDFS)
Why IndexR
IndexR的作者認為現有的各類OLAP系統均存在各種缺點,無法滿足其公司實際的OLAP需求,所以開發了IndexR。
- Mysql,PostgreSQL等關系型數據庫:無法滿足超大規模數據集。
- ES等搜索系統:對OLAP場景沒有特殊優化,在大數據量場景下內存和磁盤壓力比較大。
- Druid,Pinot等時序數據庫:在查詢條件命中大量數據情況下可能會有性能問題,而且排序、聚合等能力普遍不太好,從IndexR作者的使用經驗來看運維比較困難,靈活性和擴展性不夠,比如缺乏Join、子查詢等。
- Infobright,ClickHose等列式數據庫: 不是基於Hadoop生態的。
- Kylin:查詢靈活性不足,無法進行探索式分析。
- Impala,Presto,SparkSQL,Drill等計算引擎 + Parquet等存儲引擎:這也是IndexR的架構。IndexR的優勢是更有效的索引設計,並且支持數據實時攝入。
IndexR Architecture
- IndexR中只有一種節點IndexR Node,現在IndexR作為Drill插件嵌入了Drillbit進程,下圖是IndexR的服務部署圖:
- Drill是一個類似Presto的MPP數據庫,Drillbit是一個類似Presto Work節點的常駐進程,和Hadoop的DN進程混部,可以利用HDFS的短路讀的特性。Zookeeper主要用來存儲表和segment的一些元信息。IndexR的架構圖如下:
- IndexR支持從Kafka實時讀取數據。IndexR支持通過Drill,Hive,Spark查詢數據,不過Hive,Spark只能查詢歷史數據,Drill可以同時查詢實時數據和歷史數據。
IndexR Storage
1、基本概念
- Table:表是對用戶可見的概念,用戶的查詢需要指定Table。
- Segment:1個Table由多個Segment組成,Segment自解釋,自帶索引,是實時數據和離線數據轉換的紐帶,實時的segment和離線的segment具體結構稍有不同。
- Column: IndexR是列式存儲的,即某一列的數據會集中存放在一起。某一列的索引和數據是存放在一起的。
- Pack: 列數據在內部會進一步細分為Pack,每個Pack有65536行記錄,Pack是基本的IO和索引單位。
- Row: 表示一行數據。實時數據攝入和離線導入的時候數據都是以行為單位加入一個segment的。
2、離線Segment的存儲格式
- IndexR 在HDFS存儲的一個文件是一個Segment,一個Segment保存一個表的部分行,包含所有的列。
-
Segment 文件由4部分組成:版本號,Segment的元數據,所有Column 和 Pack的倒排索引。
-
Segment的元數據包括:行數,列數,每列的MAX和MIN值,每列的name, type,每列的各種索引的偏移量等。
-
Column包含多個Pack,每個Pack由DataPackNode,PackRSIndex,PackExtIndex,DataPack4部分組成,但是存儲的時候是先存儲所有Pack的索引數據,再存儲所有Pack的實際數據,這樣的好處是可以通過只讀取索引文件來快讀過濾掉不必要的Pack,來減少隨機IO。
-
-
圖中DataPack是實際的數據;
-
DataPackNode是Pack元數據信息,包括索引文件的大小和偏移等;
-
PackRSIndex是Pack的Rough Set索引;
-
PackExtIndex是Pack的內索引,包括equal,in, greater, between, like 5種。
-
圖中的outerIndex是Pack級別的倒排索引,主要用於Pack之間的精准過濾。
-
3、實時Segment
- 實時Segment存儲在實時節點 本機的文件系統,和離線Segment的主要區別是每個Column的數據,元數據,索引都是單獨一個文件。
- 實時節點會定期的對本機的實時Segment進行merge,將多個segment合並為一個segment,並將所有Column寫入一個segment文件中。 基本原理和Druid類似,Durid(一): 原理架構
IndexR Index
- IndexR的3層索引,依次是Rough Set Index(粗糙集索引), Inverted Index(倒排索引),PackExtIndex(內索引),如下圖
1、Rough Set Index
- RSIndex的思路和Bloomfilter一樣,可以快速判斷某個值是否在某個Pack中。RSIndex的構建過程十分簡單,就是將Pack中某一列的所有值進行N等分, 如果這列的區間長度m小於1024,則N等於m,否則N等於1024。然后將每個值映射到這N個區間,每個區間用1個bit表示。如下圖
- 對於如下的date列:因為區間長度(20170110 - 20170101 = 9)小於1024,所以每個值對應的bit就是和該列最小值的差值,所以生成的RSIndex如下,value等於1表示存儲,等於0表示不存在。
- 所以當我們查詢: SELECT column FROM A WHERE date = '20170104' 時, 我們知道 20170104 的value是0,所以確定20170104不在該pack,可以直接跳過。
- 由於Pack內的數據是根據維度有序的,每個Pack總共有65536行記錄,所有有很大概率1個Pack的維度列的基數是小於1024的。所以RSIndex的索引文件很小,而且索引效率較高。
2、Inverted Index
- IndexR對於需要倒排索引的列會建立倒排索引,用於Pack之間的精准過濾。 倒排索引的構建過程如下:
- 首先Pack內部會使用紅黑樹對value進行字典編碼,然后將字典保存下來。
- 在生成離線 Segment的時候,每一列會建立倒排索引。
- 倒排索引會保留每個value到packID的映射。
- 查詢時會根據value找到對應的packID。
3、PackExtIndex
- PackExtIndex是Pack的擴展索引,包括equal, in, greater, between, like 5種,主要用於查詢時的對於Pack內部數據的快速過濾。PackExtIndex的實現方式有兩種,一種是基於字典的,一種是基於bit的簡單索引。
4、有了Inverted Index為什么還需要RSIndex
- 一個很明顯的問題,既然倒排索引已經可以很精准的對Pack進行過濾,為啥還多此一舉再加個粗糙集索引呢? 因為倒排索引是可選項,而且存儲成本較高。
IndexR 常見問題
1、數據實時攝入如何實現
- 實現思路和Druid基本類似,實時節點直接從Kafka拉取數據,生成RT Segment。
2、IndexR如何支持Hive查詢
- 實現了IndexRInputFormat 和 IndexROutputFormat。
3、IndexR 如何支持Spark查詢
- 實現了IndexRFileFormat,該類實現了接口org.apache.spark.sql.execution.datasources.FileFormat。
4、IndexR 如何整合Drill
- IndexR主要負責存儲層,作為Drill的1個存儲插件,還會對具體的查詢過程進行優化,比如常見的條件下推,limit下推。
5、IndexR 的存儲性能
- 作者聲稱VLT模式的Segment的Scan速度比Parquet快2倍,而且僅需要 75%的存儲。Basic模式的Segment使用了Infobright的壓縮算法,可以實現極高的壓縮比。
6、IndexR 如何實現Schema的在線更新
- 當addColumn,deleteColumn,alterColumn時,生成新的SegmentSchema,然后通過MR job生成新的Segment,當Job commit時,刪除舊的Segment,並將新Segment從tmp目錄move到標准目錄,最后通知該Segment已更新。
7、IndexR 堆外內存的實現
- 利用sun.misc.Unsafe直接操作堆外內存。
- 像C語言一樣直接用指針從內存get值,用指針直接set值。
- 讀取文件時直接讀寫到DirectByteBuffer。
- 用到DirectByteBuffer的類一定及時釋放。
IndexR 亮點
- 豐富的索引。
- 豐富的謂詞下推。
- 只有一種節點,外部依賴較少。
- 同時支持OLAP和明細查詢。
- 支持Schema在線更新。
- 使用堆外內存避免GC。
- 壓縮算法使用C++實現。
IndexR 不足
- 數據類型僅支持int, long, float, double, string。
- 聚合函數僅支持sum, max, min, first, last。
- Drillbit和DN混部,可能會影響HDFS的穩定性。
- 強依賴Drill,必須先部署Drill。
IndexR 是最快的數據庫嗎?
-
顯然不是!
-
沒有預計算的系統肯定不會是最快的OLAP數據庫,對於需要大量Scan,大量計算,大量聚合的SQL, 不經過預計算則不可能實現秒級查詢。IndexR的作者顯然也知道這個問題,所以提出了父子表的概念,也就是對於一些查詢經常用到的高頻維度組合,可以把這些高頻的維度組合提前計算出來,作為一張子表,這就是預計算的思想,和Kylin能夠保證Cube和HBase的透明性相比,IndexR必須要求應用層實現表的路由,並且查詢時需要明確的指定不同表的名稱。
-
其實實際業務的查詢一般也是符合二八定律的,我們只需要將高頻的20%的維度組合預計算出來,就可以滿足80%查詢的性能要求。 Kylin一直在維度組合優化上努力,而360也在Druid中引入了類似Kylin中cubooid的概念。
-
所以我們可以得出,要想打造出一個高並發,足夠穩定,秒級響應的OLAP系統,預計算肯定是必要的,但關鍵是我們需要在預計算的度上進行自動化,智能化的把控。
參考資料