Apache Doris在OLAP方案的發展歷程和原理解析

Apache Doris(incubating)從2008年第一個版本開始到今天已經走過了11個年頭。期間,Doris 從最初的只為解決百度鳳巢報表的專用系統,已經成長為目前國內唯一的分析型數據庫孵化項目。一路走來, Doris 的初心從未改變。

Apache Doris —— 為分析而生

從誕生之日起,Doris 的每一步都是為了解決切實的業務痛點,每一次轉變都是在面對不同的業務挑戰。一路上,Doris 砥礪前行,凝結了眾多前輩的心血。相信未來,Doris 還會有更多的新鮮血液加入,我們一起走的更快、更遠。

Doris 發展歷程

Doris 自第一版誕生以來,經過了11年的發展,中間做過無數改進。這隻羅列對 Doris 發展來說比較重要的關鍵節點與事件。

Apache Doris在OLAP方案的發展歷程和原理解析

#2008 Doris1,“築巢引鳳”的重要基石

早年,百度最主要的收入來源是廣告。廣告主需要通過報表服務來查看廣告的展現、點擊、消費等信息,並且能夠需要通過不同維度來獲得廣告的消費情況,用以指導後續的廣告的投放策略。

在 Doris1 誕生之前,百度使用 MySQL Sharding 方式來為廣告主提供廣告報表支持。隨著百度本身流量的增加,廣告流量也隨之增加,已有的 MySQL Sharding 方案變得不再能夠滿足業務的需求。主要體現在以下幾個方面:

第一,大規模數據導入會導致 MySQL 的讀性能大幅降低,甚至還有鎖表情況,在密集導入數據的情況下尤為明顯。同時在數據導入時,MySQL 的查詢性能大幅下降,導致頁面打開很緩慢或者超時,用戶體驗很差;第二,MySQL 在大查詢方面性能很差,因此只能從產品層面來限制用戶的查詢時間範圍,用戶體驗很差;第三,MySQL 對數據量的支持是有限的。單表存儲的數據有限,如果過大,查詢就會變慢。對此的解決方案只有拆表、拆庫、遷移數據。隨著數據量的快速增長,已經無法維護。

當時數據存儲和計算成熟的開源產品很少,Hbase 的導入性能只有大約2000條/秒,不能滿足業務每小時新增的要求。而業務還在不斷增長,來自業務的壓力越來越大。在這種情況下,Doris1 誕生了,並且在2008年10月份跟隨百度鳳巢系統一起正式上線。

Apache Doris在OLAP方案的發展歷程和原理解析





Doris1 的主要架構如上圖所示。數據仍然通過用戶 ID 進行 Hash,將同一個用戶 ID 的數據交由一臺機器處理。其中 Hm-Storage 負責數據的存儲。ODP、OMG 負責將業務數據導入到 Hm-Storage 中。AS 負責解析、規劃查詢請求,並將查詢請求發給 Hm-Storage 處理,並對 Hm-Storage 返回的數據進行一些業務相關的計算後將查詢結果返回給用戶。

相比於 MySQL 的方案,Doris1 主要在如下幾個方面進行了改進。

首先,Doris1 的數據模型將數據分為 Key 列、Value 列。比如一條數據的 Key 列包括:用戶 ID、時間、地域、來源等等,Value 列包括:展現次數、點擊次數、消費額等。這樣的數據模型下,所有 Key 列相同的數據 Value 列能夠進行聚合,比如數據的時間維度最細粒度為小時,那同一小時多次導入的數據是能夠被合併成一條的。這樣對於同樣的查詢來說,Doris1 需要掃描的數據條目相比 MySQL 就會降低很多。

其次,Doris1 將 MySQL 逐條插入改成了批量更新,並且通過外圍模塊將同一批次數據進行排序以及預聚合。這樣一個批次中相同 Key 的數據能夠被預先聚合,另外排序後的數據能夠在查詢的時候起到聚集索引的作用,提升查詢時候的性能。

最後,Doris1 提供了天表、月表這種類似物化視圖的功能。比如用戶是想將數據按天進行匯聚展現,那麼對於這種查詢是可以通過天表來滿足的。而天表相對於小時表數據量會小几倍,相應的查詢性能也會提升幾倍。

通過 Doris1 的工作,完全解決了 MySQL Sharding 遇到的問題。並於2008年10月在鳳巢系統一起上線,完美地支撐了廣告統計報表需求。

#2009 Doris2,解“百度統計”燃眉之急

2008年的百度統計服務大約有50-60臺 MySQL,但是業務每天有3000萬+條增量數據,由於 MySQL 的存儲和查詢性能無法滿足需求,對存量數據的支撐已經到了極限,問題頻出,萬般無奈之下百度統計甚至關閉了新增用戶的功能,以減少數據量的增加。

Doris1 由於當時時間緊、任務重,所以設計、實現的時候只為了能夠滿足鳳巢的業務需求,並沒有兼顧其他的應用需求。由於 Doris1 方案對於鳳巢成功的支持,百度統計同學開始基於 Doris1 打造 Doris2 系統,主要將 Doris1 進行通用化改造,包括支持自定義 schema 等,使 Doris 能夠應用於其他產品。此外還進行一些優化以此來提升系統的查詢、存儲性能。

2009年 Doris2 研發完成後上線百度統計,並且成功支撐百度統計後續的快速增長,成功助力百度統計成為當時國內規模最大,性能、功能最強的統計平臺。由於在鳳巢、百度統計上的成功,公司內部後續其他類似統計報表類的需求也都由 Doris2 進行支持,比如網盟、聯盟等報表服務。

#2010 Doris3 ,讓查詢再快一點

百度在2009-2011年發展迅猛,營收每年近100%的速度增長,與之相伴的是廣告數據量也隨之大幅增長。隨著業務數據量的不斷增長,Doris2 系統的問題也逐漸成為業務發展的瓶頸。

首先體現在 Doris2 無法滿足業務的查詢性能需求,主要是對於長時間跨度的查詢請求、以及大客戶的查詢請求。這是因為 Doris2 通過規則將全部數據按照用戶 ID 進行 Sharding,這雖然能夠將全部數據分散到多臺機器上,但是對於單一用戶的數據還是全部落在一臺機器上。隨著單一用戶數據量增多,一些查詢請求無法快速計算得到結果。

其次,Doris2 在日常運維方面基本上都需要停服後手動操作,比如 Schema Change、集群擴縮容等,一方面用戶體驗很差,一方面還會增加集群運維的成本。最後,Doris2 本身並不是高可用系統,機器故障等問題還是會影響服務的穩定性,並且需要人肉進行復雜的操作來恢復服務。

為了解決 Doris2 的問題,團隊開始了 Doris3 的設計、研發。Doris3 的主要架構如下圖所示,其中 DT(Data Transfer)負責數據導入、DS(Data Seacher)模塊負責數據查詢、DM(Data Master)模塊負責集群元數據管理,數據則存儲在 Armor 分佈式 Key-Value 引擎中。Doris3 依賴 ZooKeeper 存儲元數據,從而其他模塊依賴 ZooKeeper 做到了無狀態,進而整個系統能夠做到無故障單點。

Apache Doris在OLAP方案的發展歷程和原理解析

在數據分佈方面 Doris3 引入了分區的概念。首先數據會按照時間進行分區(比如天分區、月分區);在同一個分區裡,數據會根據用戶 ID 再進行 Sharding。這樣同一個用戶的數據會落在不同的分區上,而在查詢時多臺機器就能夠同時處理一個用戶的數據了,實現了單用戶的分佈式計算能力。但是可能還會存在一個分區內部單個用戶數據量過大的情況。對於這種情況 Doris3 設計了後續表功能,會將單個分區內大用戶的數據進行拆分,導入到多個分片中,這樣能夠保證每個分片內單個用戶的數據總量最高是有限度的。

另外 Doris3 在日常運維 Schema Change,以及擴容、縮容等方面都做了針對性設計,使其能夠自動化進行,不依賴線上人工操作。

在當時,由於種種原因,Doris3 最終確定使用了 Armor 來作為底層存儲系統。Armor 是一款分佈式 Key-Value 系統,支持多副本強一致,且單表內全 Key 有序。選用 Armor 作為底層存儲能夠使 Doris3 只負責管理分片,而分片的副本,以及副本的一致性都由 Armor 來處理。並且,集群的擴、縮容等操作也只需要 Armor 感知即可,Doris3 本身並不需要感知。當然除了這些好處外,這樣的選型也有一些弊端。

由於 Armor 是一個通用的 Key-Value 系統,並不感知上層的業務數據,它並不支持 Doris 這種數據模型,相同 Key 的數據,Value 字段是可以進行聚合的。比如數據導入的批次是五分鐘一批,但是數據時間粒度是小時,那麼其實一個小時的數據可能是多次導入的,但是邏輯上是可以合併成一條數據的。所以為了實現這個功能,只能是 Doris3 自身實現了較為複雜的數據合併策略來完成相關數據的合併。

Doris3 在2011年完成開發後逐漸替換 Doris2 所製成的業務,並且成功解決了大客戶查詢的問題。而公司內部後續的新需求,也都由 Doris3 來承擔支持。

#2012 MySQL + Doris3 ,百度的第一個 OLAP 平臺

2012年隨著 Doris3 逐步遷移 Doris2 的同時,大數據時代悄然到來。在公司內部,隨著百度業務的發展,各個業務端需要更加靈活的方式來分析已有的數據。而此時的 Doris3 仍然只支持單表的統計分析查詢,還不能夠滿足業務進行多維分析的需求。由於缺少通用的 SQL 支持,Doris3 在面對更加靈活的多維分析場景時有點力不從心。當時,公司內只有 Hive 以及類似系統支持大數據量的 SQL 查詢,但是他們均是面向解決離線分析場景,而在線多維分析領域缺少一款產品來滿足業務方的需求。

所以,為了能夠支持業務的多維分析需求,Doris3 採用了 MySQL Storage Handler 的方式來擴展 Doris3。通過此種方式,將 Doris3 偽裝成一個 MySQL 的存儲後端,類似於 MyISAM、InnoDB 一樣。這樣既能夠利用上 MySQL 對於 SQL 的支持,也能利用上 Doris3 對於大數據量的支持。由於這裡 MySQL 是計算單點,為了減輕 MySQL 的計算壓力,Doris3 應用了 MySQL 的 BKA(Batched Key Access)以及 MRR(Multi-Range Read)等機制儘量將計算下推到 Doris3 來完成,從而減輕 MySQL 的計算壓力。

Apache Doris在OLAP方案的發展歷程和原理解析

通過 MySQL + Doris3 這個方案,百度 Insight 團隊為 PS、LBS、WISE 等產品線提供了百度內部第一個 OLAP 分析服務平臺。

#2012 OLAP Engine,突破底層存儲束縛

另一方面 Doris3 支持報表分析場景時,底層通用 Key-Value 存儲引擎的弊端也逐漸顯露。

第一,由於 Key-Value 系統讀取只能夠讀取全 Key,全 Value,而報表分析系統中的大部分查詢並不需要讀取所有列,這樣會帶來不必要的 IO 開銷;第二,正如前文所說,由於引擎本身不感知業務模型,不能夠再進行 Merge 的同時完成數據的合併,這需要 Doris3 藉助複雜的作業管理在引擎外部完成 Merge 工作既不簡潔、也不高效;第三,為了保證業務的導入原子性,Doris3 為每批次導入都賦值一個版本號,並記錄在每條數據 Key 的最後部分。這樣在查詢的時候,需要對每條數據進行 Key 的解析,比較版本號,過濾掉不需要的版本。這樣一方面需要讀取無需讀取的數據,一方面需要解析所有 Key,從而帶來不必要的 CPU 開銷;第四,Key-Value 系統無法感知數據內容,只能使用通用壓縮算法,進而導致數據的壓縮效率不高。這樣在查詢、讀取時都會帶來較多的 IO 負載。

為了能夠在底層存儲引擎上有所突破,OLAP Engine項目啟動了。這個項目的發起者是當時從 Google 來的高 T,為百度帶來了當時業界最領先的底層報表引擎技術。OLAP Engine 最大的特點包括以下幾點。

第一,引擎端原生就支持 Schema,並且所有的列分為 Key 列,Value 列。這樣就能夠跟上層的業務模型能夠對應上,查詢部分列時,無需加載全部列,減少不必要的 IO 開銷。

第二,獨特的數據模型。Value 列支持聚合操作,包括 SUM、MIN、MAX 等。在 Key 列相同的情況下,Value 列就能夠按照聚合操作類型完成對應的聚合操作。而引擎本身導入方式類似於 LSM Tree,這樣在引擎後臺進行 Merge 的同時,就能夠將相同 Key 的數據中的 Value 字段按照對應的操作進行聚合。這樣就無需外部再進行數據合併作業管理,將引擎層與業務層合併合二為一,省去不必要的 IO、CPU 開銷。

第三,數據批量導入,原子生效。對於每個批次的導入,都會有個 Delta 文件對應,並且會有個版本號。在查詢的時候只是在初始化的時候來確定讀取哪個文件,這樣就只會讀取生效版本的數據,而不會讀取沒有生效版本的數據,更不會浪費 CPU 來進行版本號比較過濾。

第四,行列式存儲。多行(比如1024行)數據存儲在一個 Block 內,Block 內相同列的數據一同壓縮存放,這樣可以根據數據特徵利用不同的壓縮算法(比如對於時間字段使用 RLE 等)大幅提高數據壓縮效率。

即使分佈式層沒有采用複雜的分佈式管理,只是使用類似 Doris2 的用戶 ID Sharding 方式,OLAP Engine 後續也成功地支持了鳳巢,網盟等廣告業務。這充分體現了 OLAP Engine 強大的報表分析能力。雖然 OLAP Engine 取得了成功,但是由於硬 Sharding 方案帶來的不易運維、不易擴展等問題仍然存在。

#2013 用 PALO,玩轉 OLAP

底層技術的發展會激發上層業務的需求,而上層業務的需求同時會為底層的技術帶來新的挑戰。隨著第一款 OLAP 產品的問世,數據分析師們的建模就更加複雜,有時查詢 SQL 會有上千行,人為閱讀已經相當吃力。而 MySQL + Doris3 方案的弊端也就越發突顯。因為分析 SQL 越來越複雜,大量的計算都需要在 MySQL 中完成,這樣 MySQL 的計算能力就成為整個系統的性能瓶頸,突破這個性能瓶頸也就變得極為緊迫。

因此 Doris 亟需一款擁有分佈式計算能力的查詢引擎。幸運的是當時(2013年)各種 SQL on Hadoop 項目也正蓬勃發展,比如 Impala,Tajo,Presto 等等。在有限的時間內並不充分調研的情況下,團隊選取了 Impala 作為了後續系統的分佈式查詢引擎。當時選擇 Impala 主要的原因是因為其性能較高,並且 BE 的 C++ 語言跟我們已有系統的語言一致,未來可以省去一部分序列化開銷。

由於 MySQL + Doris3 的方案制約了業務的使用,當時公司的另一個團隊邀請了 Oracle 的 Exadata 進行 POC,這給了 Doris 團隊很大的壓力。如果 Doris 想繼續在 OLAP 領域繼續發展,就需要快速產出原型,並且性能上還要勝出 Exadata。為了快速驗證方案的可行性,團隊幾個月內就把 Impala 與 Doris3 進行了集成,並用 TPC-H 進行了測試,結果是 Impala + Doris3 性能比 Exadata 更好。這次原型的成功為我們贏得了一次機會,能夠讓團隊繼續改造 Doris3 從而更好地支持 OLAP 場景。

新產品的名字命名為 PALO,意為玩轉 OLAP。

PALO1 除了增加分佈式查詢層之外,因為 OLAP Engine 在統計報表領域的成功,PALO1 放棄了 Doris3 依賴的通用 Key-Value 系統,選擇了 OLAP Engine 作為自己的單機引擎。因為沒有了分佈式 Key-Value 系統,那麼 PALO1 自己完成數據分片管理、副本管理等工作。

PALO1 的架構如下所示。其中 DM 負責管理元數據、數據的分佈、分片副本管理等內容,DM 本身沒有狀態,元數據內容都存儲在 MySQL 中。FE 負責接收用戶的查詢請求,並且進行查詢規劃解析。BE 是負責存儲數據,以及進行具體的查詢執行。

Apache Doris在OLAP方案的發展歷程和原理解析

隨著 PALO1 的正式上線,除了遷移所有 Doris3 已有的業務外,也成功支持了當時百度內部大部分的 OLAP 分析場景。

#2015 PALO2,讓架構再簡單一點

如果說 PALO1 是為了解決性能問題,那麼 PALO2 主要是為了在架構上進行優化。由於 PALO1 模塊數目較多,並且外部依賴 MySQL,這其實還是增加了運維的壓力的。所以我們在 PALO2 項目中力求將系統的架構進行簡化。經過簡化後的系統架構如下圖所示。

Apache Doris在OLAP方案的發展歷程和原理解析

PALO2 中我們只存在2種模塊:FE、BE。FE 一方面負責管理、存儲元數據,另一方面 FE 還負責與用戶交互,接受用戶查詢,對查詢規劃,監督查詢執行,並將查詢結果返回給用戶。FE 本身是有狀態的,但是它內部通過 BDB JE,能夠將元數據進行多副本複製,從而能夠保證服務的高可用。BE 與 PALO1 功能一致,只是 PALO2 的 BE 包含了存儲引擎,一方面減少了一個模塊,並且在用戶查詢的時候少了一次數據的序列化、反序列化操作,節約 CPU 消耗。

通過 PALO2 的工作,系統架構本身變得相當簡潔,並且不需要任何依賴。因為 PALO2 架構的簡潔,我們後續也相對容易的基於 PALO2 提供了公有云服務以及私有化部署;另一方面,當 PALO 開源之後其他用戶也能夠用通過較低的門檻來搭建使用 PALO 。在此之後 PALO 雖然經過幾次改進,但是整體架構仍然保持 PALO2 的架構。

#2017 and Future Apache Doris (incubating) ,是更廣闊的世界

PALO2 在百度內部基本服務了所有的統計報表、多維分析需求,我們相信它一定可以應用到其他公司,能夠幫助更多的人更加高效、方便地支持類似的業務需求。因此,我們選擇了開源,PALO 於2017年正式在 GitHub 上開源,並且在2018年貢獻給 Apache 社區,並將名字改為 Apache Doris(incubating) 進行正式孵化。貢獻給 Apache 之後,Doris 就不僅僅是百度的項目,而成為了 Apache 的項目。

隨著開源,Doris 已經在京東、美團、搜狐、小米等公司的生產環境中正式使用,也有越來越多的Contributor 加入到 Doris 大家庭中。一路走來,Doris 從未懼怕過挑戰,也從未被困難擊倒。時至今日,Doris 已經站在了更高的舞臺上,準備擁抱更多的機遇與挑戰。

希望未來,會有更多的人來續寫這篇 Doris 簡史,講述這個為分析而生的故事。


分享到:


相關文章: