萬字長文揭秘:阿里如何實現海量資料實時分析?

阿里技術發表於2018-12-19

挑戰

隨著資料量的快速增長,越來越多的企業迎來業務資料化時代,資料成為了最重要的生產資料和業務升級依據。伴隨著業務對海量資料實時分析的需求越來越多,資料分析技術這兩年也迎來了一些新的挑戰和變革:

  • 線上化和高可用,離線和線上的邊界越來越模糊,一切資料皆服務化、一切分析皆線上化。

  • 高併發低延時,越來越多的資料系統直接服務終端客戶,對系統的併發和處理延時提出了新的互動性挑戰。

  • 混合負載, 一套實時分析系統既要支援資料加工處理,又要支援高併發低延時的互動式查詢

  • 融合分析, 隨著對資料新的使用方式探索,需要解決結構化與非結構化資料融合場景下的資料檢索和分析問題。

阿里巴巴最初透過單節點Oracle進行準實時分析, 後來轉到Oracle RAC,隨著業務的飛速發展, 集中式的Shared Storage架構需要快速轉向分散式,遷移到了Greenplum,但不到一年時間便遇到擴充套件性和併發的嚴重瓶頸。為了迎接更大資料集、更高併發、更高可用、更實時的資料應用發展趨勢,從2011年開始,線上分析這個技術領域,阿里實時數倉堅定的走上了自研之路。

萬字長文揭秘:阿里如何實現海量資料實時分析?

分析型資料庫AnalyticDB

AnalyticDB是阿里巴巴自主研發、唯一經過超大規模以及核心業務驗證的PB級實時資料倉儲。自2012年第一次在集團釋出上線以來,至今已累計迭代釋出近百個版本,支撐起集團內的電商、廣告、菜鳥、文娛、飛豬等眾多線上分析業務。

AnalyticDB於2014年在阿里雲開始正式對外輸出,支撐行業既包括傳統的大中型企業和政府機構,也包括眾多的網際網路公司,覆蓋外部十幾個行業。AnalyticDB承接著阿里巴巴廣告營銷、商家資料服務、菜鳥物流、盒馬新零售等眾多核心業務的高併發分析處理, 每年雙十一上述眾多實時分析業務高峰驅動著AnalyticDB不斷的架構演進和技術創新。

經過這2年的演進和創新,AnalyticDB已經成長為相容MySQL 5.x系列、並在此基礎上增強支援ANSI SQL:2003的OLAP標準(如window function)的通用實時數倉,躋身為實時數倉領域極具行業競爭力的產品。近期,AnalyticDB成功入選了全球權威IT諮詢機構Forrester釋出"The Forrester Wave™: CloudData Warehouse,Q4 2018"研究報告的Contenders象限,以及Gartner釋出的分析型資料管理平臺報告 (Magic Quadrant forData Management Solutions for Analytics),開始進入全球分析市場。AnalyticDB旨在幫客戶將整個資料分析和價值化從傳統的離線分析帶到下一代的線上實時分析模式。

整體架構

經過過去2年的架構演進和功能迭代,AnalyticDB當前整體架構如下圖。

AnalyticDB是一個支援多租戶的Cloud Native Realtime Data Warehouse平臺,每個租戶DB的資源隔離,每個DB都有相應獨立的模組(圖中的Front Node, Compute Node, Buffer Node),在處理實時寫入和查詢時,這些模組都是資源(CPU, Memory)使用密集型的服務,需要進行DB間隔離保證服務質量。同時從功能完整性和成本最佳化層面考慮,又有一系列叢集級別服務(圖中綠色部分模組)。

萬字長文揭秘:阿里如何實現海量資料實時分析?

下面是對每個模組的具體描述:

DB級別服務元件:

  • Front Node:負責JDBC, ODBC協議層接入,認證和鑑權,SQL解析、重寫;分割槽地址路由和版本管理;同時最佳化器,執行計劃和MPP計算的排程模組也在Front Node。

  • Compute Node: 包含MPP計算Worker模組,和儲存模組(行列混存,後設資料,索引)。

  • Buffer Node: 負責實時寫入,並根據實時資料大小觸發索引構建和合並。

叢集級別服務元件:

  • Management Console: 管理控制檯。

  • Admin Service:叢集管控服務,負責計量計費,例項生命週期管理等商業化功能,同時提供OpenAPI和InnerAPI給Management Console和第三方呼叫。

  • Global Meta Service:全域性元資料管理,提供每個DB的元資料管理服務,同時提供分割槽分配,副本管理,版本管理,分散式DDL等能力。

  • Job Service:作業服務,提供非同步作業排程能力。非同步作業包括索引構建、擴容、無縫升級、刪庫刪表的後臺非同步資料清理等。

  • Connector Service:資料來源連線服務,負責外部各資料來源(圖中右側部分)接入到AnalyticDB。目前該服務開發基本完成,即將上線提供雲服務。

  • Monitoring & Alerting Service:監控告警診斷服務,既提供面向內部人員的運維監控告警診斷平臺,又作為資料來源透過Management Console面向使用者側提供資料庫監控服務。

  • Resource Management Service:資源管理服務,負責叢集級別和DB級別服務的建立、刪除、DNS/SLB掛載/解除安裝、擴縮容、升降配,無縫升級、服務發現、服務健康檢查與恢復。

資料模型

AnalyticDB中表組(Table Group)分為兩類:事實表組和維度表組。

  • 事實表組(Fact Table Group),表組在AnalyticDB裡是一個邏輯概念,使用者可以將業務上關聯性比較多的事實表放在同一個事實表組下,主要是為了方便客戶做眾多資料業務表的管理,同時還可以加速Co-location Join計算。

  • 維度表組(Dimension Table Group),用於存放維度表,目前有且僅有一個,在資料庫建立時會自動建立,維度表特徵上是一種資料量較小但是需要和事實表進行潛在關聯的表。

AnalyticDB中表分為事實表(Fact Table)和維度表(Dimension Table)。

事實表建立時至少要指定Hash分割槽列和相關分割槽資訊,並且指定存放在一個表組中,同時支援List二級分割槽。

  • Hash Partition將資料按照分割槽列進行hash分割槽,hash分割槽被分佈到多個Compute Node中。

  • List Partition(如果指定List分割槽列的話)對一個hash分割槽進行再分割槽,一般按照時間(如每天一個list分割槽)。

  • 一個Hash Partition的所有List Partition預設存放於同一個Compute Node中。每個Hash Partition配有多個副本(通常為雙副本),分佈在不同的Compute      Node中,做到高可用和高併發。

維度表可以和任意表組的任意表進行關聯,並且建立時不需要配置分割槽資訊,但是對單表資料量大小有所限制,並且需要消耗更多的儲存資源,會被儲存在每個屬於該DB的Compute Node中。

下圖描述了從Database到List分割槽到資料模型:

萬字長文揭秘:阿里如何實現海量資料實時分析?

對於Compute Node 來說,事實表的每個List分割槽是一個物理儲存單元(如果沒有指定List分割槽列,可認為該Hash分割槽只有一個List分割槽)。一個分割槽物理儲存單元採用行列混存模式,配合後設資料和索引,提供高效查詢

海量資料

基於上述資料模型,AnalyticDB提供了單庫PB級資料實時分析能力。以下是生產環境的真實資料:

  • 阿里巴巴集團某營銷應用單DB表數超過20000張

  • 雲上某企業客戶單DB資料量近3PB,單日分析查詢次數超過1億

  • 阿里巴巴集團內某單個AnalyticDB叢集超過2000臺節點規模

  • 雲上某業務實時寫入壓力高達1000w TPS

  • 菜鳥網路某資料業務極度複雜分析場景,查詢QPS 100+

匯入匯出

靈活的資料匯入匯出能力對一個實時數倉來說至關重要,AnalyticDB當前既支援透過阿里雲資料傳輸服務DTS、DataWorks資料整合從各種外部資料來源匯入入庫,同時也在不斷完善自身的資料匯入能力。整體匯入匯出能力如下圖(其中匯入部分資料來源當前已支援,部分在開發中,即將釋出)。

萬字長文揭秘:阿里如何實現海量資料實時分析?

★ 資料匯入

首先,由於AnalyticDB相容MySQL5.x系列,支援透過MySQL JDBC方式把資料insert入庫。為了獲得最佳寫入效能,AnalyticDB提供了Client SDK,實現分割槽聚合寫的最佳化,相比透過JDBC單條insert,寫入效能有10倍以上提升。對於應用端業務邏輯需要直接寫入AnalyticDB的場景,推薦使用AnalyticDB Client SDK。

同時,對於快速上傳本地結構化的文字檔案,可以使用基於AnalyticDB Client SDK開發的Uploader工具。對於特別大的檔案,可以拆分後使用uploader工具進行並行匯入。

另外,對於OSS,MaxCompute這樣的外部資料來源,AnalyticDB透過分散式的Connector Service資料匯入服務併發讀取並寫入到相應DB中。Connector Service還將支援訂閱模式,從Kafka,MQ,RDS等動態資料來源把資料匯入到相應DB中。AnalyticDB對大資料生態的Logstash,Fluentd,Flume等日誌收集端、ETL工具等透過相應外掛支援,能夠快速把資料寫入相應DB。

今天在阿里巴巴集團內,每天有數萬張表從MaxCompute匯入到AnalyticDB中進行線上分析,其中大量匯入任務單表資料大小在TB級、資料量近千億。

★ 資料匯出

AnalyticDB目前支援資料匯出到OSS和MaxCompute,業務場景主要是把相應查詢結果在外部儲存進行儲存歸檔,實現原理類似insert from select操作。insert from select是把查詢結果寫入到內部表,而匯出操作則是寫入外部儲存, 透過改進實現機制,可以方便地支援更多的匯出資料來源。

核心技術

高效能SQL Parser

AnalyticDB經過數年的發展,語法解析器也經歷了多次更新迭代。曾經使用過業界主流的 Antlr(http://www.antlr.org),JavaCC(https://javacc.org)等Parser生成器作為SQL 語法解析器,但是兩者在長期、大規模、複雜查詢場景下,Parser的效能、語法相容、API設計等方面不滿足要求,於是我們引入了自研的SQL Parser元件FastSQL。

★ 領先業界的Parser效能

AnalyticDB主打的場景是高併發、低延時的線上化分析,對SQL Parser效能要求很高,批次實時寫入等場景要求更加苛刻。FastSQL透過多種技術最佳化提升Parser效能,例如:

  • 快速對比:使用64位hash演算法加速關鍵字匹配,使用fnv_1a_64 hash演算法,在讀取identifier的同時計算好hash值,並利用hash64低碰撞機率的特點,使用64位hash code直接比較,比常規Lexer先讀取identifier,在查詢SymbolTable速度更快。

  • 高效能的數值Parser:Java自帶的Integer.parseInt()/Float.parseFloat()需要構造字串再做parse,FastSQL改進後可以直接在原文字上邊讀取邊計算數值。

  • 分支預測:在insert values中,出現常量字面值的機率比出現其他的token要高得多,透過分支預測可以減少判斷提升效能。

以TPC-DS99個Query對比來看,FastSQL比Antlr Parser(使用Antlr生成)平均快20倍,比JSQLParser(使用JavaCC生成)平均快30倍,在批次Insert場景、多列查詢場景下,使用FastSQL後速度提升30~50倍。

萬字長文揭秘:阿里如何實現海量資料實時分析?

★ 無縫結合最佳化器

在結合AnalyticDB的最佳化器的SQL最佳化實踐中,FastSQL不斷將SQL Rewrite的最佳化能力前置化到SQL Parser中實現,透過與最佳化器的SQL最佳化能力協商,將盡可能多的表示式級別最佳化前置化到SQL Parser中,使得最佳化器能更加專注於基於代價和成本的最佳化(CBO,Cost-Based Optimization)上,讓最佳化器能更多的集中在理解計算執行計劃最佳化上。FastSQL在AST Tree上實現了許多SQL Rewrite的能力,例如:

  • 常量摺疊

SELECT * FROM t1 t
WHERE comm_week 
  BETWEEN CAST(date_format(date_add('day',-day_of_week('20180605'),
                             date('20180605')),'%Y%m%d') AS bigint)
        AND CAST(date_format(date_add('day',-day_of_week('20180605')
                            ,date('20180605')),'%Y%m%d') AS bigint)
------>
SELECT * FROM t1 t
WHERE comm_week BETWEEN20180602AND20180602
  • 函式變換:

SELECT * FROM t1 t
WHERE DATE_FORMAT(t."pay_time",'%Y%m%d')>='20180529'
    AND DATE_FORMAT(t."pay_time",'%Y%m%d')<='20180529'
------>
SELECT * FROM t1 t
WHERE t."pay_time">= TIMESTAMP'2018-05-29 00:00:00'
AND t."pay_time"< TIMESTAMP'2018-05-30 00:00:00'
  • 表示式轉換:

SELECT a, b FROM t1
WHERE b +1=10;
------>
SELECT a, b FROM t1
WHERE b =9;
  • 函式型別推斷:

-- f3型別是TIMESTAMP型別
SELECT concat(f3,1)
FROM nation;
------>
SELECT concat(CAST(f3 AS CHAR),'1')
FROM nation;
  • 常量推斷:

SELECT * FROM t
WHERE a < b AND b = c AND a =5
------>
SELECT * FROM t
WHERE b >5AND a =5AND b = c
  • 語義去重:

SELECT * FROM t1
WHERE max_adate >'2017-05-01'
    AND max_adate !='2017-04-01'
------>
SELECT * FROM t1
WHERE max_adate > DATE '2017-05-01'

玄武儲存引擎

為保證大吞吐寫入,以及高併發低時延響應,AnalyticDB自研儲存引擎玄武,採用多項創新的技術架構。玄武儲存引擎採用讀/寫例項分離架構,讀節點和寫節點可分別獨立擴充套件,提供寫入吞吐或者查詢計算能力。在此架構下大吞吐資料寫入不影響查詢分析效能。同時玄武儲存引擎構築了智慧全索引體系,保證絕大部分計算基於索引完成,保證任意組合條件查詢的毫秒級響應。

讀寫分離架構支援大吞吐寫入

傳統資料倉儲並沒有將讀和寫分開處理,即這些資料庫程式/執行緒處理請求的時候,不管讀寫都會在同一個例項的處理鏈路上進行。因此所有的請求都共享同一份資源(記憶體資源、鎖資源、IO資源),並相互影響。在查詢請求和寫入吞吐都很高的時候,會存在嚴重的資源競爭,導致查詢效能和寫入吞吐都下降。

為了解決這個問題,玄武儲存引擎設計了讀寫分離的架構。如下圖所示,玄武儲存引擎有兩類關鍵的節點:Buffer Node和Compute Node。Buffer Node專門負責處理寫請求,Compute Node專門負責查詢請求,Buffer Node和Compute Node完全獨立並互相不影響,因此,讀寫請求會在兩個完全不相同的鏈路中處理。上層的Front Node會把讀寫請求分別路由給Buffer Node和Compute Node。

萬字長文揭秘:阿里如何實現海量資料實時分析?

實時寫入鏈路:

  • 業務實時資料透過JDBC/ODBC協議寫入到Front Node。

  • Front Node根據實時資料的hash分割槽列值,路由到相應Buffer Node。

  • Buffer Node將該實時資料的內容(類似於WAL)提交到盤古分散式檔案系統,同時更新實時資料版本,並返回Front  Node,Front Node返回寫入成功響應到客戶端。

  • Buffer Node同時會非同步地把實時資料內容推送到Compute Node,Compute Node消費該實時資料並構建實時資料輕量級索引。

  • 當實時資料積攢到一定量時,Buffer Node觸發後臺Merge Baseline作業,對實時資料構建完全索引並與基線資料合併。

實時查詢鏈路:

  • 業務實時查詢請求透過JDBC/ODBC協議傳送到Front Node。

  • Front Node首先從Buffer Node拿到當前最新的實時資料版本,並把該版本隨執行計劃一起下發到Compute Node。

  • Compute Node檢查本地實時資料版本是否滿足實時查詢要求,若滿足,則直接執行並返回資料。若不滿足,需先到Buffer Node把指定版本的實時資料拖到本地,再執行查詢,以保證查詢的實時性(強一致)。

AnalyticDB提供強實時和弱實時兩種模式,強實時模式執行邏輯描述如上。弱實時模式下,Front Node查詢請求則不帶版本下發,返回結果的實時取決於Compute Node對實時資料的處理速度,一般有秒極延遲。所以強實時在保證資料一致性的前提下,當實時資料寫入量比較大時對查詢效能會有一定的影響。

高可靠性

玄武儲存引擎為Buffer Node和Compute Node提供了高可靠機制。使用者可以定義Buffer Node和Compute Node的副本數目(預設為2),玄武保證同一個資料分割槽的不同副本一定是存放在不同的物理機器上。Compute Node的組成採用了對等的熱副本服務機制,所有Compute Node節點都可以參與計算。另外,Computed Node的正常執行並不會受到Buffer Node節點異常的影響。如果Buffer Node節點異常導致Compute Node無法正常拉取最新版本的資料,Compute Node會直接從盤古上獲取資料(即便這樣需要忍受更高的延遲)來保證查詢的正常執行。資料在Compute Node上也是備份儲存。如下圖所示,資料是透過分割槽存放在不同的ComputeNode上,具有相同hash值的分割槽會儲存在同一個Compute Node上。資料分割槽的副本會儲存在其他不同的Compute Node上,以提供高可靠性。

萬字長文揭秘:阿里如何實現海量資料實時分析?

高擴充套件性

玄武的兩個重要特性設計保證了其高可擴充套件性:1)Compute Node和Buffer Node都是無狀態的,他們可以根據業務負載需求進行任意的增減;2)玄武並不實際儲存資料,而是將資料存到底層的盤古系統中,這樣,當Compute Node和Buffer Node的數量進行改變時,並不需要進行實際的資料遷移工作。

為計算而生的儲存

資料儲存格式

傳統關係型資料庫一般採用行儲存(Row-oriented Storage)加B-tree索引,優勢在於其讀取多列或所有列(SELECT *)場景下的效能,典型的例子如MySQL的InnoDB引擎。但是在讀取單列、少數列並且行數很多的場景下,行儲存會存在嚴重的讀放大問題。

資料倉儲系統一般採用列儲存(Column-oriented Storage),優勢在於其單列或少數列查詢場景下的效能、更高的壓縮率(很多時候一個列的資料具有相似性,並且根據不同列的值型別可以採用不同的壓縮演算法)、列聚合計算(SUM, AVG, MAX, etc.)場景下的效能。但是如果使用者想要讀取整行的資料,列儲存會帶來大量的隨機IO,影響系統效能。

為了發揮行儲存和列儲存各自的優勢,同時避免兩者的缺點,AnalyticDB設計並實現了全新的行列混存模式。如下圖所示:

萬字長文揭秘:阿里如何實現海量資料實時分析?

  • 對於一張表,每k行資料組成一個Row Group。在每個Row Group中,每列資料連續的存放在單獨的block中,每Row Group在磁碟上連續存放。

  • Row Group內列block的資料可按指定列(聚集列)排序存放,好處是在按該列查詢時顯著減少磁碟隨機IO次數。

  • 每個列block可開啟壓縮。

行列混存儲存相應的後設資料包括:分割槽後設資料,列後設資料,列block後設資料。其中分割槽後設資料包含該分割槽總行數,單個block中的列行數等資訊;列後設資料包括該列值型別、整列的MAX/MIN值、NULL值數目、直方圖資訊等,用於加速查詢;列block後設資料包含該列在單個Row Group中對應的MAX/MIN/SUM、總條目數(COUNT)等資訊,同樣用於加速查詢

全索引計算

使用者的複雜查詢可能會涉及到各種不同的列,為了保證使用者的複雜查詢能夠得到秒級響應,玄武儲存引擎在行列混合儲存的基礎上,為基線資料(即歷史資料)所有列都構建了索引。玄武會根據列的資料特徵和空間消耗情況自動選擇構建倒排索引、點陣圖索引或區間樹索引等,而用的最多的是倒排索引。

萬字長文揭秘:阿里如何實現海量資料實時分析?

如上圖所示,在倒排索引中,每列的數值對應索引的key,該數值對應的行號對應索引的value,同時所有索引的key都會進行排序。依靠全列索引,交集、並集、差集等資料庫基礎操作可以高效能地完成。如下圖所示,使用者的一個複雜查詢包含著對任意列的條件篩選。玄武會根據每個列的條件,去索引中篩選滿足條件的行號,然後再將每列篩選出的行號,進行交、並、差操作,篩選出最終滿足所有條件的行號。玄武會依據這些行號去訪問實際的資料,並返回給使用者。通常經過篩選後,滿足條件的行數可能只佔總行數的萬分之一到十萬分之一。因此,全列索引幫助玄武在執行查詢請求的時候,大大減小需要實際遍歷的行數,進而大幅提升查詢效能,滿足任意複雜查詢秒級響應的需求。

萬字長文揭秘:阿里如何實現海量資料實時分析?

使用全列索引給設計帶來了一個很大挑戰:需要對大量資料構建索引,這會是一個非常耗時的過程。如果像傳統資料庫那樣在資料寫入的路徑上進行索引構建,那麼這會嚴重影響寫入的吞吐,而且會嚴重拖慢查詢的效能,影響使用者體驗。為了解決這個挑戰,玄武採用了非同步構建索引的方式。當寫入請求到達後,玄武把寫SQL持久化到盤古,然後直接返回,並不進行索引的構建。

當這些未構建索引的資料(稱為實時資料)積累到一定數量時,玄武會開啟多個MapReduce任務,來對這些實時資料進行索引的構建,並將實時資料及其索引,同當前版本的基線資料(歷史資料)及其索引進行多版本歸併,形成新版本的基線資料和索引。這些MapReduce任務透過伏羲進行分散式排程和執行,非同步地完成索引的構建。這種非同步構建索引的方式,既不影響AnalyticDB的高吞吐寫入,也不影響AnalyticDB的高效能查詢

非同步構建索引的機制還會引入一個新問題:在進行MapReduce構建索引的任務之前,新寫入的實時資料是沒有索引的,如果使用者的查詢會涉及到實時資料,查詢效能有可能會受到影響。玄武採用為實時資料構建排序索引(Sorted Index)的機制來解決這個問題。

如下圖所示,玄武在將實時資料以block形式刷到磁碟之前,會根據每一列的實時資料生成對應的排序索引。排序索引實際是一個行號陣列,對於升序排序索引來說,行號陣列的第一個數值是實時資料最小值對應的行號,第二個數值是實時資料第二小值對應的行號,以此類推。這種情況下,對實時資料的搜尋複雜度會從O(N)降低為O(lgN)。排序索引大小通常很小(60KB左右),因此,排序索引可以快取在記憶體中,以加速查詢

萬字長文揭秘:阿里如何實現海量資料實時分析?

羲和計算引擎

針對低延遲高併發的線上分析場景需求,AnalyticDB自研了羲和大規模分析引擎,其中包括了基於流水線模型的分散式平行計算引擎,以及基於規則 (Rule-Based Optimizer,RBO) 和代價(Cost-Based Optimizer,CBO)的智慧查詢最佳化器

★   最佳化器

最佳化規則的豐富程度是能否產生最優計劃的一個重要指標。因為只有可選方案足夠多時,才有可能選到最優的執行計劃。AnalyticDB提供了豐富的關係代數轉換規則,用來確保不會遺漏最優計劃。

基礎最佳化規則:

  • 裁剪規則:列裁剪、分割槽裁剪、子查詢裁剪

  • 下推/合併規則:謂詞下推、函式下推、聚合下推、Limit下推

  • 去重規則:Project去重、Exchange去重、Sort去重

  • 常量摺疊/謂詞推導

探測最佳化規則:

  • Joins:BroadcastHashJoin、RedistributedHashJoin、NestLoopIndexJoin

  • Aggregate:HashAggregate、SingleAggregate

  • JoinReordering

  • GroupBy下推、Exchange下推、Sort下推

高階最佳化規則:CTE

例如下圖中,CTE的最佳化規則的實現將兩部分相同的執行邏輯合為一個。透過類似於最長公共子序列的演算法,對整個執行計劃進行遍歷,並對一些可以忽略的運算元進行特殊處理,如Projection,最終達到減少計算的目的。

萬字長文揭秘:阿里如何實現海量資料實時分析?

單純基於規則的最佳化器往往過於依賴規則的順序,同樣的規則不同的順序會導致生成的計劃完全不同,結合基於代價的最佳化器則可以透過嘗試各種可能的執行計劃,達到全域性最優。

AnalyticDB的代價最佳化器基於Cascade模型,執行計劃經過Transform模組進行了等價關係代數變換,對可能的等價執行計劃,估算出按Cost Model量化的計劃代價,並從中最終選擇出代價最小的執行計劃透過Plan Generation模組輸出,存入Plan Cache(計劃快取),以降低下一次相同查詢的最佳化時間。

萬字長文揭秘:阿里如何實現海量資料實時分析?

線上分析的場景對最佳化器有很高的要求,AnalyticDB為此開發了三個關鍵特性:儲存感知最佳化、動態統計資訊收集和計劃快取。

儲存層感知最佳化

生成分散式執行計劃時,AnalyticDB最佳化器可以充分利用底層儲存的特性,特別是在Join策略選擇,Join Reorder和謂詞下推方面。

  • 底層資料的雜湊分佈策略將會影響Join策略的選擇。基於規則的最佳化器,在生成Join的執行計劃時,如果對資料物理分佈特性的不感知,會強制增加一個資料重分佈的運算元來保證其執行語義的正確。 資料重分佈帶來的物理開銷非常大,涉及到資料的序列化、反序列化、網路開銷等等,因此避免多次資料重分佈對於分散式計算是非常重要的。除此之外,最佳化器也會考慮對資料庫索引的使用,進一步減少Join過程中構建雜湊的開銷。

  • 調整Join順序時,如果大多數Join是在分割槽列,最佳化器將避免生成Bushy Tree,而更偏向使用Left Deep Tree,並儘量使用現有索引進行查詢。

萬字長文揭秘:阿里如何實現海量資料實時分析?

  • 最佳化器更近一步下推了謂詞和聚合。聚合函式,比如count(),和查詢過濾可以直接基於索引計算。

所有這些組合降低了查詢延遲,同時提高叢集利用率,從而使得AnalyticDB能輕鬆支援高併發。

動態統計資訊收集

統計資訊是最佳化器在做基於代價查詢最佳化所需的基本資訊,通常包括有關表、列和索引等的統計資訊。傳統資料倉儲僅收集有限的統計資訊,例如列上典型的最常值(MFV)。商業資料庫為使用者提供了收集統計資訊的工具,但這通常取決於DBA的經驗,依賴DBA來決定收集哪些統計資料,並依賴於服務或工具供應商。

上述方法收集的統計資料通常都是靜態的,它可能需要在一段時間後,或者當資料更改達到一定程度,來重新收集。但是,隨著業務應用程式變得越來越複雜和動態,預定義的統計資訊收集可能無法以更有針對性的方式幫助查詢。例如,使用者可以選擇不同的聚合列和列數,其組合可能會有很大差異。但是,在查詢生成之前很難預測這樣的組合。因此,很難在統計收集時決定正確統計方案。但是,此類統計資訊可幫助最佳化器做出正確決定。

我們設計了一個查詢驅動的動態統計資訊收集機制來解決此問題。守護程式動態監視傳入的查詢工作負載和特點以提取其查詢模式,並基於查詢模式,分析缺失和有益的統計資料。在此分析和預測之上,非同步統計資訊收集任務在後臺執行。這項工作旨在減少收集不必要的統計資料,同時使大多數即將到來的查詢受益。對於前面提到的聚合示例,收集多列統計資訊通常很昂貴,尤其是當使用者表有大量列的時候。根據我們的動態工作負載分析和預測,可以做到僅收集必要的多列統計資訊,同時,最佳化器能夠利用這些統計資料來估計聚合中不同選項的成本並做出正確的決策。

計劃快取

從線上應用案件看,大多數客戶都有一個共同的特點,他們經常反覆提交類似的查詢。在這種情況下,計劃快取變得至關重要。為了提高快取命中率,AnalyticDB不使用原始SQL文字作為搜尋鍵來快取。相反,SQL語句首先透過重寫並引數化來提取模式。例如,查詢 “SELECT * FROM t1 WHERE a = 5 + 5”將轉化為“SELECT * FROM t1 WHERE a =?”。引數化的SQL模版將被作為計劃快取的關鍵字,如果快取命中,AnalyticDB將根據新查詢進行引數繫結。由於這個改動,即使使用有限的快取大小,最佳化器在生產環境也可以保持高達90%以上的命中率,而之前只能達到40%的命中率。

這種方法仍然有一個問題。假設我們在列a上有索引,“SELECT * FROM t1 WHERE a = 5”的最佳化計劃可以將索引掃描作為其最佳訪問路徑。但是,如果新查詢是“SELECT * FROM t1 WHERE a = 0”並且直方圖告訴我們數值0在表t1佔大多數,那麼索引掃描可能不如全表掃描有效。在這種情況下,使用快取中的計劃並不是一個好的決定。為了避免這類問題,AnalyticDB提供了一個功能Literal Classification,使用列的直方圖對該列的值進行分類,僅當與模式相關聯的常量“5”的資料分佈與新查詢中常量“0”的資料分佈類似時,才實際使用快取記憶體的計劃。否則,仍會對新查詢執行常規最佳化。

★ 執行引擎

最佳化器之下,AnalyticDB在MPP架構基礎上,採用流水線執行的DAG架構,構建了一個適用於低延遲和高吞吐量工作負載的執行器。如下圖所示,當涉及到多個表之間非分割槽列JOIN時,CN(MPP Worker)會先進行data exchange (shuffling)然後再本地JOIN (SourceTask),aggregate後傳送到上一個stage(MiddleTask),最後彙總到Output Task。由於絕大多情況都是in-memory計算(除複雜ETL類查詢,儘量無中間Stage 落盤)且各個stage之間都是pipeline方式協作,效能上要比MapReduce方式快一個數量級。萬字長文揭秘:阿里如何實現海量資料實時分析?

在接下來的幾節中,將介紹其中三種特性,包括混合工作負載管理,CodeGen和向量化執行。

混合工作負載管理

作為一套完備的實時數倉解決方案,AnalyticDB中既有需要較低響應時間的高併發查詢,也有類似ETL的批處理,兩者爭用相同資源。傳統數倉體系往往在這兩個方面的兼顧性上做的不夠好。

AnalyticDB worker接收coordinator下發的任務, 負責該任務的物理執行計劃的實際執行。這項任務可以來自不同的查詢, worker會將任務中的物理執行計劃按照既定的轉換規則轉換成對應的operator,物理執行計劃中的每一個Stage會被轉換成一個或多個operator。

萬字長文揭秘:阿里如何實現海量資料實時分析?

執行引擎已經可以做到stage/operator級別中斷和Page級別換入換出,同時執行緒池在所有同時執行的查詢間共享。但是,這之上仍然需要確保高優先順序查詢可以獲得更多計算資源。

萬字長文揭秘:阿里如何實現海量資料實時分析?

根據經驗,客戶總是期望他們的短查詢即使當系統負載很重的時候也能快速完成。為了滿足這些要求,基於以上場景,透過時間片的分配比例來體現不同查詢的優先順序,AnalyticDB實現了一個簡單版本的類Linux kernel 的排程演算法。系統記錄了每一個查詢的總執行耗時,查詢總耗時又是透過每一個Task耗時來進行加權統計的,最終在查詢層面形成了一顆紅黑樹,每次總是挑選最左側節點進行排程,每次取出或者加入(被喚醒以及重新入隊)都會重新更新這棵樹,同樣的,在Task被喚醒加入這顆樹的時候,執行引擎考慮了補償機制,即時間片耗時如果遠遠低於其他Task的耗時,確保其在整個樹裡面的位置,同時也避免了因為長時間的阻塞造成的飢餓,類似於CFS 排程演算法中的vruntime補償機制。

萬字長文揭秘:阿里如何實現海量資料實時分析?

這個設計雖然有效解決了慢查詢佔滿資源,導致其他查詢得不到執行的問題,卻無法保障快查詢的請求延遲。這是由於軟體層面的多執行緒執行機制,執行緒個數大於了實際的CPU個數。在實際的應用中,計算執行緒的個數往往是可用Core的2倍。這也就是說,即使快查詢的運算元得到了計算執行緒資源進行計算,也會在CPU層面與慢查詢的運算元形成競爭。所下圖所示,快查詢的運算元計算執行緒被排程到VCore1上,該運算元在VCore1上會與慢查詢的計算執行緒形成競爭。另外在物理Core0上,也會與VCore0上的慢查詢的計算執行緒形成競爭。

萬字長文揭秘:阿里如何實現海量資料實時分析?

在Kernel sched模組中,對於不同優先順序的執行緒之間的搶佔機制,已經比較完善,且時效性比較高。因而,透過引入kernel層面的控制可以有效解決快查詢低延遲的問題,且無需對運算元的實現進行任何的改造。執行引擎讓高優先順序的執行緒來執行快查詢的運算元,低優先順序的執行緒來執行慢查詢的運算元。由於高優先順序執行緒搶佔低優先順序執行緒的機制,快查詢運算元自然會搶佔慢查詢的運算元。此外,由於高優先順序執行緒在Kernel sched模組排程中,具有較高的優先順序,也避免了快慢查詢運算元在vcore層面的CPU競爭。

萬字長文揭秘:阿里如何實現海量資料實時分析?

同樣的在實際應用中是很難要求使用者來辨別快慢查詢,因為使用者的業務本身可能就沒有快慢業務之分。另外對於線上查詢查詢的計算量也是不可預知的。為此,計算引擎在Runtime層面引入了快慢查詢的識別機制,參考Linux kernel中vruntime的方式,對運算元的執行時間、排程次數等資訊進行統計,當運算元的計算量達到給定的慢查詢的閾值後,會把運算元從高優先順序的執行緒轉移到低優先順序的執行緒中。這有效提高了在壓力測試下快查詢的響應時間。

程式碼生成器

Dynamic code generation(CodeGen)普遍出現在業界的各大計算引擎設計實現中。它不僅能夠提供靈活的實現,減少程式碼開發量,同樣在效能最佳化方面也有著較多的應用。但是同時基於ANTLR ASM的AnalyticDB程式碼生成器也引入了數十毫秒編譯等待時間,這在實時分析場景中是不可接受的。為了進一步減少這種延遲,分析引擎使用了快取來重用生成的Java位元組碼。但是,它並非能對所有情況都起很好作用。

隨著業務的廣泛使用以及對效能的進一步追求,系統針對具體的情況對CodeGen做了進一步的最佳化。使用了Loading Cache對已經生成的動態程式碼進行快取,但是SQL表示式中往往會出現常量(例如,substr(col1,1, 3),col1 like‘demo%’等),在原始的生成邏輯中會直接生成常量使用。這導致很多相同的方法在遇到不同的常量值時需要生成一整套新的邏輯。這樣在高併發場景下,cache命中率很低,並且導致JDK的meta區增長速度較快,更頻繁地觸發GC,從而導致查詢延遲抖動。

substr(col1,  1, 3)

=>  cacheKey<CallExpression(substr), inputReferenceExpression(col1),  constantExpression(1), constantExpression(3)>cacheValue bytecode;

 

透過對錶達式的常量在生成bytecode階段進行rewrite,對出現的每個常量在Class級別生成對應的成員變數來儲存,去掉了Cachekey中的常量影響因素,使得可以在不同常量下使用相同的生成程式碼。命中的CodeGen將在plan階段instance級別的進行常量賦值。

substr(col1,  1, 3)

=>  cacheKey<CallExpression(substr),  inputReferenceExpression(col1)>cacheValue bytecode;

在測試與線上場景中,經過最佳化很多高併發的場景不再出現meta區的GC,這顯著增加了快取命中率,整體執行穩定性以及平均延遲均有一定的提升。

AnalyticDB CodeGen不僅實現了謂詞評估,還支援了運算元級別運算。例如,在複雜SQL且資料量較大的場景下,資料會多次shuffle複製,在partitioned shuffle進行資料複製的時候很容易出現CPU瓶頸。用於連線和聚合操作的資料Shuffle通常會複製從源資料塊到目標資料塊的行,虛擬碼如下所示:

foreach row

   foreach column

      type.append(blockSrc, position, blockDest);

從生產環境,大部分SQL每次shuffle的資料量較大,但是列很少。那麼首先想到的就是forloop的展開。那麼上面的虛擬碼就可以轉換成

foreach  row

   type(1).append(blockSrc(1), position,  blockDest(1));

   type(2).append(blockSrc(2), position,  blockDest(2));

   type(3).append(blockSrc(3), position,  blockDest(3));

 上面的最佳化透過直接編碼是無法完成的,需要根據SQL具體的column情況動態的生成對應的程式碼實現。在測試中1000w的資料量級複製延時可以提升24%。

 向量化引擎和二進位制資料處理

相對於行式計算,AnalyticDB的向量化計算由於對快取更加友好,並避免了不必要的資料載入,從而擁有了更高的效率。在這之上,AnalyticDB CodeGen也將執行態因素考慮在內,能夠輕鬆利用異構硬體的強大功能。例如,在CPU支援AVX-512指令集的叢集,AnalyticDB可以生成使用SIMD的位元組碼。同時AnalyticDB內部所有計算都是基於二進位制資料,而不是Java Object,有效避免了序列化和反序列化開銷。

極致彈性

在多租戶基礎上,AnalyticDB對每個租戶的DB支援線上升降配,擴縮容,操作過程中無需停服,對業務幾乎透明。以下圖為例:

萬字長文揭秘:阿里如何實現海量資料實時分析?

  • 使用者開始可以在雲上開通包含兩個C4資源的DB進行業務試用和上線(圖中的P1, P2...代表表的資料分割槽)

  • 隨著業務的增長,當兩個C4的儲存或計算資源無法滿足時,使用者可自主對該DB發起升配或擴容操作,升配+擴容可同時進行。該過程會按副本交替進行,保證整個過程中始終有一個副本提供服務。另外,擴容增加節點後,資料會自動在新老節點間進行重分佈。

  • 對於臨時性的業務增長(如電商大促),升配擴容操作均可逆,在大促過後,可自主進行降配縮容操作,做到靈活地成本控制。

線上升降配,平滑擴縮容能力,對今年雙十一阿里巴巴集團內和公共雲上和電商物流相關的業務庫起到了至關重要的保障作用。

GPU加速

★ 客戶業務痛點

某客戶資料業務的資料量在半年時間內由不到200TB增加到1PB,並且還在快速翻番,截止到發稿時為止已經超過1PB。該業務計算複雜,查詢時間跨度週期長,需按照任意選擇屬性過濾,單個查詢計算涉及到的運算元包括20個以上同時交併差、多表join、多值列(類似array)group by等以及上述運算元的各種複雜組合。傳統的MapReduce離線分析方案時效性差,極大限制了使用者快速分析、快速鎖定人群並即時投放廣告的訴求,業務發展面臨新的瓶頸。

★ AnalyticDB加速方案

GPU加速AnalyticDB的做法是在Compute Node中新增GPU Engine對查詢進行加速。GPU Engine主要包括: Plan Rewriter、Task Manager、Code Generator、CUDA Manager、Data Manager和VRAM Manager。

萬字長文揭秘:阿里如何實現海量資料實時分析?

SQL查詢從Front Node傳送到Compute Node,經過解析和邏輯計劃生成以後,Task Manager先根據計算的資料量以及查詢特徵選擇由CPU Engine還是GPU Engine來處理,然後根據邏輯計劃生成適合GPU執行的物理計劃。

GPU Engine收到物理計劃後先對執行計劃進行重寫。如果計劃符合融合特徵,其中多個運算元會被融合成單個複合運算元,從而大量減少運算元間臨時資料的Buffer傳輸。

Rewriting之後物理計劃進入Code Generator,該模組主功能是將物理計劃編譯成PTX程式碼。Code Generator第一步藉助LLVM JIT先將物理計劃編譯成LLVM IR,IR經過最佳化以後透過LLVMNVPTX Target轉換成PTX程式碼。CUDA執行時庫會根據指定的GPU架構型號將PTX轉換成本地可執行程式碼,並啟動其中的GPU kernel。Code Generator可以支援不同的Nvidia GPU。

CUDA Manager透過jCUDA呼叫CUDA API,用於管理和配置GPU裝置、GPU kernel的啟動介面封裝。該模組作為Java和GPU之間的橋樑,使得JVM可以很方便地呼叫GPU資源。

Data Manager主要負責資料載入,將資料從磁碟或檔案系統快取載入到指定堆外記憶體,從堆外記憶體載入到視訊記憶體。CPU Engine的執行模型是資料庫經典的火山模型,即表資料需逐行被拉取再計算。這種模型明顯會極大閒置GPU上萬行的高吞吐能力。目前Data Manager能夠批次載入列式資料塊,每次載入的資料塊大小為256M,然後透過PCIe匯流排傳至視訊記憶體。

VRAM Manager用於管理各GPU的視訊記憶體。視訊記憶體是GPU中最稀缺的資源,需要合理管理和高效複用,有別於現在市面上其他GPU資料庫系統使用GPU的方式,即每個SQL任務獨佔所有的GPU及其計算和視訊記憶體資源。為了提升視訊記憶體的利用率、提升併發能力,結合AnalyticDB多分割槽、多執行緒的特點,我們設計基於Slab的VRAM Manager統一管理所有視訊記憶體申請:Compute Node啟動時,VRAM Manager先申請所需空間並切分成固定大小的Slab,這樣可以避免執行時申請帶來的時間開銷,也降低透過顯示卡驅動頻繁分配視訊記憶體的DoS風險。

在需要視訊記憶體時,VRAM Manager會從空閒的Slab中查詢空閒區域劃分視訊記憶體,用完後返還Slab並做Buddy合併以減少視訊記憶體空洞。效能測試顯示分配時間平均為1ms,對於整體執行時間而言可忽略不計,明顯快於DDR記憶體分配的700ms耗時,也利於提高系統整體併發度。在GPU和CPU資料互動時,自維護的JVM堆外記憶體會作為JVM內部資料物件(如ByteBuffer)和視訊記憶體資料的同步緩衝區,也一定程度減少了Full GC的工作量。

GPU Engine採用即時程式碼生成技術主要有如下優點:

  • 相對傳統火山模型,減少計劃執行中的函式呼叫等,尤其是分支判斷,GPU中分支跳轉會降低執行效能

  • 靈活支援各種複雜表示式,例如projection和having中的複雜表示式。例如HAVING     SUM(double_field_foo) > 1這種表示式的GPU程式碼是即時生成的

  • 靈活支援各種資料型別和UDF查詢時追加

  • 利於運算元融合,如group-by聚合、join再加聚合的融合,即可減少中間結果(特別是Join的連線結果)的複製和視訊記憶體的佔用 

根據邏輯執行計劃動態生成GPU執行碼的整個過程如下所示:

萬字長文揭秘:阿里如何實現海量資料實時分析?

★ GPU 加速實際效果

該客戶資料業務使用了GPU實時加速後,將計算複雜、響應時間要求高、併發需求高的查詢從離線分析系統切換至AnalyticDB進行線上分析執行穩定,MapReduce離線分析的平均響應時間為5到10分鐘,高峰時可能需要30分鐘以上。無縫升級到GPU加速版AnalyticDB之後,所有查詢完全實時處理並保證秒級返回,其中80%的查詢的響應時間在2秒以內(如下圖),而節點規模降至原CPU叢集的三分之一左右。 業務目前可以隨時嘗試各種圈人標籤組合快速對人群畫像,即時鎖定廣告投放目標。據客戶方反饋,此加速技術已經幫助其在競爭中構建起高壁壘,使該業務成為同類業務的核心能力,預計明年使用者量有望翻番近一個數量級。

萬字長文揭秘:阿里如何實現海量資料實時分析?

總結

簡單對本文做個總結,AnalyticDB做到讓資料價值線上化的核心技術可歸納為:

  • 高效能SQL Parser:自研Parser元件FastSQL,極致的解析效能,無縫集合最佳化器

  • 玄武儲存引擎:資料更新實時可見,行列混存,粗糙集過濾,聚簇列,索引最佳化

  • 羲和計算引擎:MPP+DAG融合計算,CBO最佳化,向量化執行,GPU加速

  • 極致彈性:業務透明的線上升降配,擴縮容,靈活控制成本。

  • GPU加速:利用GPU硬體加速OLAP分析,大幅度降低查詢延時。

分析型資料AnalyticDB, 作為阿里巴巴自研的下一代PB級實時資料倉儲, 承載著整個集團內和雲上客戶的資料價值實時化分析的使命。 AnalyticDB為資料價值線上化而生,作為實時雲資料倉儲平臺,接下來會在體驗和周邊生態建設上繼續加快建設,希望能將最領先的下一代實時分析技術能力普惠給所有企業,幫助企業轉型加速資料價值探索和線上化。

相關文章