常見OLAP引擎對比
常見OLAP引擎包括不僅限於Hive、Spark SQL、Presto、Kylin、Impala、Druid、Clickhouse、Greeplum,目前業務系統剛用ClickHouse代替原有的MPP查詢,使用效率提高了40%
與OLTP不同,OLAP更強調SQL的執行速度,分區,強調磁盤I/O,OLTP強調事務,強調並發,強調內存效率以及命中率
OLAP目前開源的很多,但是沒有一種能完全解決所有場景,沒有一個系統能同時在數據量、性能、和靈活性三個方面做到完美,每個系統在設計時都需要在這三者間做出取舍,在實際使用過程中,由於各OLAP底層實現不同,在SQL上也有差異,所以切換業務系統的OLAP,其成本也將引起很多問題,在業務考慮上最好能考慮一種完全推進下去,目前較為主流並且應用於大型系統上的有CH,Kylin,Druid本文詳細介紹這三種,順帶介紹其余的Spark SQL等
ClickHouse
索引結構
Clickhouse 數據模型就是普通二維表,只討論索引結構。整體上 Clickhouse 的索引也是列式索引結構,每個列一個文件。Clickhouse 索引的大致思路是:首先選取部分列作為索引列,整個數據文件的數據按照索引列有序,這點類似 MySQL 的聯合索引;其次將排序后的數據每隔 8192 行選取出一行,記錄其索引值和序號,注意這里的序號不是行號,序號是從零開始並遞增的,Clickhouse 中序號被稱作 Mark’s number;然后對於每個列(索引列和非索引列),記錄 Mark’s number 與對應行的數據的 offset。
以一個二維表(date, city, action)為例介紹了整個索引結構,其中(date,city)是索引列。
以如下查詢為例看索引的使用
select count(distinct action) where date=toDate(2020-01-01) and city=’bj’
-
二分查找 primary.idx 並找到對應的 mark’s number 集合(即數據 block 集合)
-
在上一步驟中的 block 中,在 date 和 city 列中查找對應的值的行號集合,並做交集,確認行號集合
-
將行號轉換為 mark’s number 和 offset in block(注意這里的 offset 以行為單位而不是 byte)
-
在 action 列中,根據 mark’s number 和.mark 文件確認數據 block 在 bin 文件中的 offset,然后根據 offset in block 定位到具體的列值。
-
后續計算
該實例中包含了對於列的正反兩個方向的查找過程。
反向:查找 date=toDate(2020-01-01) and city=’bj’數據的行號;
正向:根據行號查找 action 列的值。對於反向查找,只有在查找條件匹配最左前綴的時候,才能剪枝掉大量數據,其它時候並不高效。
Clickhouse 小結:
- MergeTree引擎眾多,最常用並且默認的引擎是Merge Tree引擎,其分布式引擎在測試上面能提高更為復雜SQL的查詢速度,但是其分布式表是依賴於ZK的偽分布式,需要專門維護本地表做分布式表
- MergeTree Family 作為主要引擎系列,其中包含適合明細數據的場景和適合聚合數據的場景;
- Clickhouse 的索引有點類似 MySQL 的聯合索引,當查詢前綴元組能命中的時候效率最高,可是一旦不能命中,幾乎會掃描整個表,效率波動巨大;所以建表需要業務專家,這一點跟 kylin 類似。
Kylin
數據模型
Kylin 的數據模型本質上是將二維表(Hive 表)轉換為 Cube,然后將 Cube 存儲到 HBase 表中,也就是兩次轉換。
第一次轉換,其實就是傳統數據庫的 Cube 化,Cube 由 CuboId 組成,下圖每個節點都被稱為一個 CuboId,CuboId 表示固定列的數據數據集合,比如“ AB” 兩個維度組成的 CuboId 的數據集合等價於以下 SQL 的數據集合:
select A, B, sum(M), sum(N) from table group by A, B
第二次轉換,是將 Cube 中的數據存儲到 HBase 中,轉換的時候 CuboId 和維度信息序列化到 rowkey,度量列組成列簇。在轉換的時候數據進行了預聚合。下圖展示了 Cube 數據在 HBase 中的存儲方式。
索引結構
Kylin 將數據存儲到 HBase 中,所以 kylin 的數據索引就是 HBase 的索引。HBase 的索引是簡化版本的 B+樹,相比於 B+樹,HFile 沒有對數據文件的更新操作。
HFile 的索引是按照 rowkey 排序的聚簇索引,索引樹一般為二層或者三層,索引節點比 MySQL 的 B+樹大,默認是 64KB。數據查找的時候通過樹形結構定位到節點,節點內部數據是按照 rowkey 有序的,可以通過二分查找快速定位到目標。
Kylin 小結
- 適用於聚合查詢場景;因為數據預聚合,Kylin 可以說是最快的查詢引擎(group-by 查詢這樣的復雜查詢,可能只需要掃描 1 條數據);
- kylin 查詢效率取決於是否命中 CuboId,查詢波動較大;HBase 索引有點類似 MySQL 中的聯合索引,維度在 rowkey 中的排序和查詢維度組合對查詢效率影響巨大;所以 Kylin 建表需要業務專家參與
- kylin較為適合老業務系統,目前很多傳統系統都是采用Hive,Hbase 來做數倉,其切換成本很多,采用Kylin技術棧方面能較為合理,並且由於其依賴於Hadoop生態,和Hive能完美結合
- kylin目前支持實時+離線一站式解決方案,並且引入Spark來替換原有的MR構建Cube,構建時間大大縮短
- 對運維和業務要求較高,業務上需要提前購建好維度,跟查詢效率直接相關,並且運維也有較高的要求,首先必須了解HBase,Hive,MapReduce,Spark,HDFS,Yarn的原理;其次對MapReduce Job和Spark Job的問題排查和調優經驗要豐富;然后必須掌握對Cube復雜調優的方法;最后出現問題時排查的鏈路較長,復雜度較高
實際使用場景:
鏈家:Apache Kylin 實踐:鏈家數據分析引擎的演變史
Druid
數據模型
Druid 數據模型比較簡單,它將數據進行預聚合,只不過預聚合的方式與 Kylin 不同,kylin 是 Cube 化,Druid 的預聚合方式是將所有維度進行 Group-by,可以參考下圖:
索引結構
Druid 索引結構使用自定義的數據結構,整體上它是一種列式存儲結構,每個列獨立一個邏輯文件(實際上是一個物理文件,在物理文件內部標記了每個列的 start 和 offset)。對於維度列設計了索引,它的索引以 Bitmap 為核心。下圖為“city”列的索引結構:
首先將該列所有的唯一值排序,並生成一個字典,然后對於每個唯一值生成一個 Bitmap,Bitmap 的長度為數據集的總行數,每個 bit 代表對應的行的數據是否是該值。Bitmap 的下標位置和行號是一一對應的,所以可以定位到度量列,Bitmap 可以說是反向索引。同時數據結構中保留了字典編碼后的所有列值,其為正向的索引。
那么查詢如何使用索引呢?以以下查詢為例:
select site, sum(pv) from xx where date=2020-01-01 and city='bj' group by site
-
city 列中二分查找 dictionary 並找到’bj’對應的 bitmap
-
遍歷 city 列,對於每一個字典值對應的 bitmap 與’bj’的 bitmap 做與操作
-
每個相與后的 bitmap 即為 city='bj’查詢條件下的 site 的一個 group 的 pv 的索引
-
通過索引在 pv 列中查找到相應的行,並做 agg
-
后續計算
Druid 總結
- Druid 適用於聚合查詢場景但是不適合有超高基維度的場景;
- 存儲全維度 group-by 后的數據,相當於只存儲了 KYLIN Cube 的 Base-CuboID;每個維度都有創建索引,所以每個查詢都很快,並且沒有類似 KYLIN 的巨大的查詢效率波動。
有贊:Druid在有贊的實踐