Apache Doris 在有讚的初步嘗試

作者: 鄭生俊

有贊 OLAP

有贊作爲一家商家服務公司,OLAP 對有贊非常重要,從商家後臺的數據看板,再到最近有贊門羅發佈會上的有贊賈維斯,都離不開 OLAP。具體到技術棧,有讚的 MOLAP 採用 Apache Kylin(參考文章:有贊出品|升級 Kylin 4 最強攻略!),ROLAP 採用了 ClickHouse(參考文章:ClickHouse 在有讚的實踐之路)。

Apache Kylin 在有贊內部已經非常穩定了,支撐了大量的高 QPS 的場景,並且它的存儲計算分離架構能夠很好地做到彈性伸縮來應對流量的高峯低谷。而 ClickHouse 作爲 ROLAP 引擎最主要的問題有兩塊,一是擴縮容麻煩,二是單機的 Join 性能差。其中擴縮容的高成本是有贊數據團隊在 ROLAP 最爲頭疼的問題,比如有贊門羅發佈會發佈了 MA 的優惠之後,因爲 ClickHouse 擴容的時效性和複雜度,我們需要提前準備較多的硬件爲 ClickHouse 擴容,以應對有可能的業務劇增。尤其是如果 ClickHouse 之上又有實時寫入的場景、做了數據正交分佈時,擴容就難上加難了。

試水 Apache Doris

爲了解決上面的問題,團隊也嘗試了 ClickHouse on Apache Doris,但後來因爲一些計劃變動沒有繼續投入。欣慰的是 Apache Doris 在今年推出了向量化引擎,也在 6 月 15 號正式孵化爲 Apache 頂級項目,它的設計上能夠解決 ClickHouse 上述的問題。進而細看了 Apache Doris 的向量化引擎,基本上借鑑了前輩 ClickHouse 的做法。既然是站在巨人的肩膀上,我們覺得它應該是能夠承載 ClickHouse 之上的業務。爲此我們做了初步的性能測試和與 Druid 的兼容性測試。

查詢性能:

在初步的性能測試過程中,結果還是比較驚喜的,比 Druid 快很多,也比之前非向量化的版本快很多,有些場景和 ClickHouse 差不多。查詢這塊主要分享一下我們最近在 2phase aggregate 做的優化。

背景是我們測試在 merge aggregate 的過程中,發現第二階段的聚合比第一階段的聚合速度慢很多。首先解釋一下 merge aggregate,對於分佈式聚合查詢,數據通常都需要在某個算子預聚合後,再匯聚到下一個算子進行 merge 聚合。但我們從 Doris 的執行信息看到第二階段的聚合比第一階段的聚合慢不少,而測試場景下,二階段聚合的數據量比第一階段的聚合少很多,反而二階段聚合的耗時還更高了。

首先嚐試使用 perftools 來分析瓶頸點,找到核心的耗時代碼:

圖片

我們找到上面代碼,看了一下二階段聚合的大致流程,大概有這麼幾步:

  1. 反序列化一階段的聚合結果,得到 StringColumn

  2. 將 StringColumn,反序列化爲對應列的數據類型(double、long、hll 等數據結構)

  3. 將對應的數據列(double、long、hll 等數據結構),轉換爲臨時用來聚合的數據類型(AggregateData)

  4. 將臨時的聚合對象與 Hash 表中存放的最終結果(也是 AggregateData)進行聚合運算

  5. 銷燬 3 步驟產生的臨時聚合對象

結合上面的 perf 採樣圖,我們可以看到大量的 CPU 消耗在:

  1. 將 StringColumn 反序列化爲對應的數據類型,比如 long、int、hll 等

  2. 將聚合的輸入數據(long、int、hll)等轉換爲聚合需要的 AggregateData,這裏就有大量臨時 對象 / 內存 的頻繁創建和銷燬

爲了解決上述第一個問題,嘗試了在第一階段聚合結束之後,直接將結果轉換爲第二個階段需要的數據類型。這樣第二階段的數據讀取,只需要做對應指針類型的強制類型轉換即可,而不需要再從 StringColumn 反序列化獲得。

爲了解決第二個問題,起初我嘗試着分配整個塊內存來存放要聚合的臨時數據,然後最後釋放整塊內存,以此減少頻繁的內存創建和銷燬過程,但收效甚微。這時候我們去看看第一階段聚合的代碼爲啥會更快,把原因說的通俗點就是:AggregateData 可以直接與 Block 中的數據(double、long、hll 等數據結構)進行聚合運算,不需要將其轉換爲聚合的相同數據類型(AggregateData)進行聚合運算即可,這樣避免大量 對象 / 內存 頻繁的創建、銷燬。

按照上面的思路改完代碼之後,我們拿一個測試環境的例子看看性能提升,這是一個涉及到 600w + 數據讀取的查詢。查詢了多次讓磁盤數據進操作系統的 cache 之後比較 RT,查詢總耗時優化前 810ms,優化後 560ms,提升了 30% 的性能。

優化前:

圖片

優化後:

圖片

雖然這個 case 從整體上看 RT 提升 250ms,看着好像可有可無,但這是在降低了資源消耗的情況下達成,往往就意味着系統能夠有更高的吞吐。而且數據量大了、查詢更復雜之後會有更大的提升,畢竟 Aggregate 在 OLAP 中是一個高頻操作。

從整體查詢 RT 的視角,RT 容易會有波動,但算子內 MergeTime 的指標統計不會,因爲 MergeTime 指標統計的都是針對一個內存中 Block 的聚合耗時,排除了很多網絡、IO 的干擾因素。

優化前:

圖片

優化後:

圖片

從上圖可以看到 MergeTime 也大大降低了(其中 ExecTime 指標的變化可以忽略,因爲走了不同的代碼邏輯,是包含在 MergeTime 的統計之中的)。

好在 Doris 的代碼寫的挺合理的,所以這個優化涉及到的代碼量不多,有興趣可以參考代碼:https://github.com/apache/doris/pull/10618/files。

兼容性

由於我們第一階段的目標是將 Doris 替換 Apache Druid,因此我們基於 Druid 場景做了一些兼容性測試。對於平臺型團隊而言,一旦上層有較多的業務,要推動底層的技術棧迭代和替換是比較困難的,因爲通常業務方也很難抽出時間和我們陪跑做全面的兼容性測試。

因此我們在 Druid Broker 處理完查詢之後,將一個查詢的 SQL、RT 記錄數等信息發送到 Kafka。然後由一個 Kafka 消費者消費上 Druid 的請求,做 SQL 改寫,將 Druid SQL 轉換爲 Doris 的查詢語句發往 Doris 進行流量回放。

一些 SQL 的基本語法都是相同的,比較大的區別是  builtin 函數。有一些 Druid  Function 的函數的參數入參含義、個數,都和 Apache Doris 有較大不同,這導致 SQL 改寫的過程繁瑣一些,但這對於平臺型的服務團隊通常是不得不做的過程。通過流量回放一來可以知道哪些不兼容的 SQL 語句要做什麼樣的調整,二來可以通過模擬線上查詢情況的過程中確定哪些性能不符合性能預期、哪些查詢是有 Bug 的,比如 Druid 時序數據查詢中經常使用到的 time_round_function 計算有誤(參考代碼:https://github.com/apache/doris/pull/9712/files)。確定了上述的各方面的性能和兼容性沒問題後,我們才能更高效地協調業務方做一些改造工作。目前我們已經回放了一部分線上查詢,整體的業務改動點還好,性能也有較大提升。

後續計劃

至於後續的計劃,我們的目標是視資源情況推進 Doris 在有贊落地,儘量將 ClickHouse、Druid 的技術棧收斂爲 Apache Doris,解決前面提到的問題,同時也做技術棧的收斂、迭代。當然這還有一些工作要做,包括兼容性測試、性能測試,確保業務上 Doris 與 ClickHouse、Druid 有相當的體驗,爲此我們也在嘗試一些手寫 SIMD 優化關鍵執行代碼,希望最終能夠藉助 Apache Doris 解決我們 ROLAP 的痛點問題。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fWhfxWURnEXmeaWxM98jIA