資料倉儲的效能問題及解決之道

danny_2018發表於2023-03-09

隨著資料量不斷增長和業務複雜度逐漸攀升,資料處理效率面臨巨大挑戰。最典型的表現是面向分析型場景的資料倉儲效能問題越來越突出,壓力大、效能低,查詢時間長甚至查不出來,跑批跑不完造成生產事故等問題時有發生。當資料倉儲出現效能問題時便不能很好服務業務了。

傳統資料倉儲的效能解決方案

叢集,也就是採用分散式技術,依賴擴充套件硬體來提升效能,是最常見的手段。將一個大的任務拆分到各個叢集節點上同時計算自然可以獲得比單機更好的效能,即使不進行分散式計算而簡單分擔併發任務也可以減輕單節點的計算壓力。叢集解決效能問題的思路簡單粗暴,只要資料倉儲支援叢集並且任務能夠拆分就可以透過堆硬體來解決效能問題,雖然可能做不到線性提升但基本都會有效果。

叢集的缺點在於高成本。現在的大資料時代大家言必提叢集,經常不管單機效能是不是得到充分發揮,反正只要能叢集往裡擴容就行了,叢集似乎是很多人眼中的“特效藥”。但我們要注意,叢集需要更多硬體支撐成本自然高,叢集運維也需要投入更多資源。另外有些複雜多步驟計算任務由於無法拆分壓根就沒法用叢集,比如大多數的多步驟大資料量跑批任務還只能用單機(單體資料庫儲存過程)完成。叢集雖好,但並不是全能,即使財大氣粗架設叢集也並不能解決所有效能問題。

對於一些耗時較長的查詢還可以採用預計算,採用空間換時間的辦法將要用到的資料事先加工好,這樣就可以將計算複雜度降到 O(1) 大幅提升效率。預計算同樣可以解決很多效能問題,透過預彙總將要用到的資料事先加工好,用空間換時間,對多維分析場景尤其有效。

預計算的問題在於靈活性太差。我們仍然以多維分析場景為例,雖然理論上可以將所有維度組合都預計算好(這樣就可以滿足所有查詢需求),但真這樣做會發現不現實,這需要天量的儲存空間才能滿足。所以只能透過梳理業務進行部分預計算,這就大大限制了查詢範圍降低了靈活性。

其實即使能全部預計算,仍然解決不了諸如非常規聚合(如算中位數、方差)、組合聚合(如算月平均銷售額)、條件測度(如算交易金額大於 100 元以上的訂單銷售額合計)、時間段彙總(自由選擇時間段內的彙總)等情況。實際業務中的查詢需求五花八門,靈活性極強,預彙總只能解決其中的一部分甚至僅僅一小部分問題,要更大範圍、更高效率地滿足多樣的線上查詢需求還需要更有效的計算手段。

更有效的手段是最佳化引擎,讓資料倉儲在同樣的硬體資源下跑出更好的效能。這是許多廠商的工作重點,有大量工程性手段已為業界熟知,比如提供列存、向量化執行、編碼壓縮、記憶體利用等(叢集也可以算是一種工程手段),透過這些技術在一定資料規模內可以提升幾倍的計算效能,在某些場景下足夠用。但工程些手段並不能改變計算的複雜度,在資料量大和複雜度特別大的場景時,效能提升仍然常常不能滿足需求。

最佳化引擎更有效的手段是演算法層面上的(複雜度層面的提升),一個優秀的資料倉儲最佳化引擎可以猜出一個查詢語句的真正目標,從而採用更高效的演算法執行(而不以字面表達的邏輯去執行),演算法層面的改善經常可以獲得更高的效能提升。當前大部分資料倉儲仍以 SQL 作為主要查詢語言,基於 SQL 的最佳化已經做得足夠優秀。但由於 SQL 描述能力的侷限性,複雜查詢會採取非常迂迴的方法,一旦 SQL 複雜度上來最佳化引擎就很難發揮作用了(猜不出目標只能按照字面表達去執行,效能就不會提升),即最佳化引擎僅對簡單查詢有效。

舉個例子,TopN 運算時:

SELECT TOP 10 x FROM T ORDER BY x DESC

大部分資料倉儲都會最佳化,不會真排序。但是改成組內 TopN 以後:

select * from (select y,*,row_number() over (partition by y order by x desc) rn from T)where rn<=10

複雜度雖然沒有提升很多,但最佳化引擎會犯暈,猜不出這句 SQL 的真正目的,只能按照 SQL 表達的意思進行大排序而效能低下。所以,有些場景我們會事先把資料加工成寬表,這樣就可以簡化查詢從而發揮最佳化引擎的作用。雖然會付出很多代價,但為了有效利用最佳化引擎有時也只能不得已而為之。

當前幾乎所有資料倉儲技術都在競爭 SQL 能力,提供更完善的 SQL 支援、提供更強的最佳化能力、支援更大規模的叢集等等,雖然這可以最大限度地“討好”受眾很廣的 SQL 使用者,但在面向複雜計算場景時使用前面的方法往往不夠有效,效能問題仍然存在。再在 SQL 的基礎上努力(工程上的最佳化)獲得的效用也並不高,並不能從根本上解決問題。而這類問題在實際業務中並不少見,下面是幾個例子。

複雜有序計算。比如使用者行為轉換漏斗分析:使用者頁面瀏覽商品、搜尋、加購物車、下單、付款等多個有序事件,現在要統計每個步驟的使用者流失率,就需要遵循多個事件在指定時間視窗內完成、按指定次序發生才有效的原則來實現。這使用 SQL 就很難實現了,需要藉助多個子查詢(與步驟數量一致)和反覆關聯完成,有些資料倉儲甚至不能執行這種複雜語句,即使能執行,效能也很低,更難以最佳化。

多步驟大資料量跑批。複雜的跑批任務使用 SQL 效果也不好,經常需要在儲存過程中藉助遊標逐步讀取資料處理,但遊標效能很低又無法並行,最後不僅資源消耗大效能也低。同時,幾十步運算在儲存過程中需要幾千行程式碼,過程中會伴隨中間結果反覆落地,效能很差,月末年終資料量大任務多的時候就會出現在規定時間內跑不完的情況。

大資料上多指標計算。很多行業都有指標計算的需要,比如銀行的貸款業務中就包括多級分類維度、多種擔保型別,再加上客戶種類、放款方式、幣種、分支機構、日期、客戶年齡段、學歷等指標會衍生出極其龐大的指標數量,彙總這些指標時要基於大量的明細資料完成,計算時會涉及大表關聯、條件過濾、分組彙總、去重計數等多種混合運算,靈活、量大、計算複雜,同時伴隨高併發導致使用 SQL 來做很吃力,預計算不靈活,實時算又太慢。

這些問題用 SQL 很難解決,於是擴充套件 SQL 能力成為繼叢集、預計算和最佳化引擎外的第四方案。現在很多資料倉儲支援使用者自定義函式(UDF)來擴充套件計算能力,使用者可以根據實際需求編寫 UDF 以滿足自身的需要。但 UDF 的開發難度較高,這對使用者的能力有很高要求。更重要的是 UDF 仍然無法解決資料倉儲的計算效能問題,因為仍然受限於資料庫的儲存,無法根據計算特點設計更高效的資料儲存(組織)形式,很多高效能演算法就無法實施,自然無法獲得高效能。

因此,要解決這些問題就需要採用非 SQL 的方案手段,在資料庫外由程式設計師控制執行邏輯,以便更好地採用低複雜度演算法和充分利用工程手段。

於是我們看到諸如 Spark 等大資料計算引擎應運而生。Spark 提供的是一個分散式計算框架,本意還是希望透過大規模叢集來滿足算力的需要,由於基於全記憶體的設計對於超出記憶體的計算不夠友好,而且 RDD 採用的 immutable 機制,在每個計算步驟後都會複製出新的 RDD,造成記憶體和 CPU 的大量佔用和浪費,效能很低,工程手段利用的並不夠好。此外,Spark 的計算類庫也不夠豐富,缺少足夠的高效能演算法,很難實現“低複雜度演算法”的目標。加之 Scala 的使用難度很大,導致面對前面提到的那些複雜計算問題編碼難度極高,既不好寫效能也不高,這可能也是 Spark 又要擁抱 SQL 的原因之一。

傳統資料倉儲不行,外部程式設計(Spark)又難又慢,那還有什麼選擇?

從前面的內容我們不難得出這樣的結論,要解決資料倉儲的效能問題確實需要獨立於 SQL 的計算體系(像 Spark),但這個計算體系要具備既簡單又快的特點。描述複雜計算邏輯不能像 Spark 那麼複雜,甚至要比 SQL 更簡單;在計算效能上不能僅靠叢集能力,還要提供豐富的高效能演算法和工程能力從而能夠充分利用硬體資源將單機效能發揮到極致。既有快速描述低複雜度演算法的能力,又具備足夠多的工程手段。同時,如果在部署運維方面還很方便就更理想了。

esProc SPL 的解決之道

esProc SPL 是一個專門處理(半)結構化資料的計算引擎,與當前資料倉儲的能力一致。但與傳統 SQL 型資料倉儲不同,esProc 沒有繼續採用關係代數而是設計了全新的計算體系,在此基礎上提供了 SPL(Structured Process Language)語法。SPL 相對 SQL 提供了更多的資料型別和運算、更豐富的計算類庫,描述能力更強,在過程計算的加持下可以按照自然思維編寫演算法,不必繞,也更低程式碼,可以很好應對前面場景中的多步驟複雜計算,相對其他硬編碼以及 SQL 的實現方式更簡單。

在效能方面,esProc SPL 提供了很多“更低複雜度”的高效能演算法來保證計算效能。我們知道,軟體改變不了硬體的效能,想要在同等硬體條件下獲得更高的計算效能只能設計更低複雜度的演算法讓計算機少執行一些基本運算,這樣自然就變快了。但是演算法不僅要想出來,還要能實現,寫得越簡單越好,所以說寫得簡單和跑得快其實是一回事。

SPL 提供的部分高效能演算法,其中很多都是 SPL 的獨創發明

當然,高效能演算法還離不開良好的資料組織,即資料儲存。像有序歸併、單邊分堆都要求資料有序才能實施。但是資料庫的儲存相對封閉,外界無法干預,無法根據計算特徵設計儲存。基於這樣的原因,SPL 提供了自有的二進位制檔案儲存,將資料儲存在庫外的檔案系統中,以便充分利用列存、有序、壓縮、並行分段等資料儲存優勢,實現根據計算特性來靈活組織資料,充分發揮高效能演算法效力。

除了這些高效能演算法,esProc 還提供了眾多工程手段來提升計算效能,列存、壓縮編碼、大記憶體以及向量式計算等等。如前所述,這些工程手段雖然無法改變計算的複雜度,但使用後經常能獲得數倍的效能提升,再疊加 SPL 內建的眾多低複雜度演算法,效能提升一兩個數量級是常態。

前面說過,基於非 SQL 體系要獲得高效能需要由程式設計師控制執行邏輯,採用低複雜度演算法,並且充分利用工程手段。SPL 理論體系的不同帶來了描述能力強的效果,編碼簡單不必繞;豐富的高效能演算法庫及相應儲存機制可以直接使用,實現採用低複雜度演算法的同時充分工程最佳化手段的目標,達到既簡單又快的效果。

像 TopN 在 SPL 中被看成普通的聚合運算,無論對全集和分組都是一樣的,都不需要大排序,這樣就實現了“採用更低複雜度演算法”的目標,從而獲得了高效能。

SPL 在有序計算的支援下實現程式碼更加簡短,可以根據自然思維分步(過程化支援)編寫程式碼,而且這段程式碼可以處理任意步驟的漏斗分析(這裡是 3 步,更多步只需要改變引數即可),相對 SQL 每增加一步漏斗就要增加一個子查詢顯然更有優勢,這就是 SPL 的簡單帶來的效果。

效能上,這個例子其實是實際案例的簡化版(原 SQL 有近 200 行),使用者使用 Snowflake 的 Medium 伺服器(相當於 4*8=32 核)3 分鐘沒有跑出來;而 esProc SPL 程式碼在一個 12 核 1.7G 的低端伺服器上僅用不到10 秒就跑出來了,這是 SPL 高效能演算法和相應工程手段造就的高效能。

有了這些機制以後,esProc SPL 就可以充分利用硬體資源,將單機效能發揮到極致,不僅原來很多單機效能問題可以得到有效解決,甚至很多原來使用叢集的計算現在也可以用單機搞定(可能更快),達到單機全能的效果。當然,單機有極限,SPL 也提供了分散式叢集功能,當單機效能無論如何也無法滿足需要時可以透過叢集橫向擴充套件算力。這也是 SPL 的高效能運算理念:先把單機效能提升到極致,不夠用再叢集。

當然,任何技術都有不足,SPL 也不例外。SQL 經過幾十年的積累發展,很多資料庫都擁有很強的最佳化引擎。對於適合用 SQL 完成的簡單場景運算,可以將普通程式設計師寫出來的慢語句最佳化出較好的效能,從這個意義上講,對程式設計師的要求相對較低。某些場景(比如多維分析)已經被最佳化多年,某些 SQL 引擎也可以跑出相當好的極致效能。相比之下,SPL 沒有做多少自動最佳化的功能,要跑出高效能,幾乎全靠程式設計師寫出低複雜度的程式碼。程式設計師需要經過一定的訓練來熟悉 SPL 的理念和庫函式,會多一個上手的門檻,不過獲得數量級的效能提升和成倍的成本下降,這些付出通常也還是值得的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31547898/viewspace-2938810/,如需轉載,請註明出處,否則將追究法律責任。

相關文章