Uber 是如何低成本構建開源大資料平臺的?

語言: CN / TW / HK

隨著 Uber 業務的擴張,為公司業務提供支援的基礎資料池也在飛速膨脹,其處理成本水漲船高。當大資料成為我們最大的運維支出專案之一後,我們啟動了一項降低資料平臺成本的計劃。該計劃將問題分解為三大分支:平臺效率、供應和需求。在這篇文章中,我們將討論 Uber 為提高資料平臺效率和降低成本所做的一系列工作。

大資料檔案格式優化

我們的大部分 Apache®Hadoop®檔案系統(HDFS)空間都被 Apache Hive 表佔用了。這些表以 Apache Parquet 檔案格式或 Apache ORC 檔案格式儲存。儘管我們計劃在未來的某個時候將它們統一整合到 Parquet,但由於許多特殊需求(包括特定條件下的相容性和效能),我們尚未實現這一目標。

Parquet 和 ORC 檔案格式都是基於塊的列格式,這意味著檔案包含許多塊,每個塊包含大量的行(比如 10,000 行),儲存在列中。

我們花了很多時間來分析 HDFS 上的檔案,並決定進行以下優化工作,主要針對 Parquet 格式:

  1. 壓縮演算法:預設情況下,我們使用 GZIP Level 6 作為 Parquet 內部的壓縮演算法。最近關於 Parquet 支援 Facebook 的 ZSTD 演算法的社群進展引起了我們的注意。在我們的實驗中,與基於 GZIP 的 Parquet 檔案相比,ZSTD Level 9 和 Level 19 能夠將我們的 Parquet 檔案大小分別減少 8%和 12%。此外,ZSTD Level 9 和 Level 19 的解壓速度都比 GZIP Level 6 要快。我們決定採用 ZSTD Level 9 重新壓縮我們過期 1 個月後的資料,並採用 ZSTD Level 19 壓縮我們過期 3 個月後的資料。這是因為在我們的實驗中,ZSTD Level 9 比 Level 19 快兩倍。請注意,重新壓縮作業是後臺維護作業,可以使用無保證的計算資源執行。鑑於此類資源相當豐富,我們基本上可以將這些重壓縮作業視為免費的。

  1. 列刪除:我們的許多 Hive 表——尤其是從 Apache Kafka®日誌中提取的表——都包含許多列,其中一些還是巢狀的。當我們檢視這些列時,很明顯,其中一些列沒有必要長期保留。比如說為了除錯每個 Kafka 訊息的元資料,以及由於合規性原因需要在一段時間後刪除的各種欄位都可以刪掉。這種列格式讓我們在技術上可以做到刪除檔案內的一些列時無需解壓和重新壓縮其他列。這讓列刪除成為了一種非常節省 CPU 的操作。我們在 Uber 實現了這樣一個特性,並將它大量用於我們的 Hive 表,還把 程式碼 貢獻回了 Apache Parquet。

  1. 行重排序:行順序可以顯著影響壓縮後 Parquet 檔案的大小。這是由於 Parquet 格式中的執行長度(Run-Length)編碼特性,以及壓縮演算法利用區域性重複的能力造成的。我們檢查了 Uber 最大的一些 Hive 表,並對排序做了手動調整,將表大小減少了 50%以上。我們發現的一個常見模式是簡單地按使用者 ID 對行排序,然後是按日誌表的時間戳排序。大多數日誌表都有使用者 ID 和時間戳列。這讓我們能夠非常高效地壓縮與使用者 ID 關聯的許多非規範化列。

  1. Delta 編碼:我們開始按時間戳對行排序後,很快就注意到了 Delta 編碼可以幫助我們進一步減少資料大小。因為與時間戳值本身相比,相鄰時間戳之間的差異非常小。在某些情況下,日誌具有穩定的節奏,就像心跳一樣,因此這種差異是恆定的。但是,在我們廣泛使用 Apache Hive、Presto®和 Apache Spark 的環境中,如 StackOverflow 問題 中所述,在 Parquet 中啟用 Delta 編碼並非易事。我們還在探索這個方向。

HDFS 糾刪碼

糾刪碼(Erasure Coding)可以顯著減少 HDFS 檔案的複製因子。由於這種技術會增加 IOPS 負載,所以在 Uber,我們主要研究 3+2 和 6+3 模式,對應的複製因子分別為 1.67 倍和 1.5 倍。鑑於預設的 HDFS 複製因子是 3 倍,也就是說我們可以將 HDD 空間需求減少近一半!

不過,糾刪碼還有多種選擇:

  1. Apache Hadoop 3.0 HDFS 糾刪 碼 :這是在 Apache Hadoop 3.0 中實現的官方糾刪碼。這個實現的好處是它同時適用於大檔案和小檔案。其缺點是 IO 效率不高,因為糾刪碼的塊非常碎片化。

  1. 客戶端糾刪碼:這種編碼首先由 Facebook 在 HDFS-RAID 專案中實現。這種方法的好處是它的 IO 效率非常高。當所有塊都可用時,讀取 IO 效率與塊進行 3 路複製的基線相當。缺點是它不適用於小檔案,因為每個塊都是糾刪碼計算的一個單位。

在諮詢了行業專家後,我們決定採用 Apache Hadoop 3.0 HDFS 糾刪碼,因為這是社群的方向。我們仍處於 Apache Hadoop 3.0 HDFS 糾刪碼的評估階段,但我們相信這種技術將顯著降低我們的 HDFS 成本。

YARN 排程策略改進

在 Uber,我們使用 Apache YARN 來執行大部分的大資料計算負載(Presto 除外,它直接執行在專用伺服器上)。就像其他很多公司一樣,我們一開始用的是 YARN 中的標準容量排程器(Capacity Scheduler)。容量排程使我們可以為每個佇列配置具有 MIN 和 MAX 設定的分層佇列結構。我們建立了一個以組織為第一級的 2 級佇列結構,允許使用者根據子團隊、優先順序或作業型別建立第二級佇列。

雖然容量排程器為我們管理 YARN 佇列容量的工作提供了一個良好的開端,但我們很快就遇到了管理 YARN 叢集容量的困境:

  1. 高利用率:我們希望 YARN 叢集的平均利用率(以分配的 CPU 和 MemGB/叢集的總 CPU 和 MemGB 容量衡量)儘可能高;

  2. 滿足使用者期望:我們希望給使用者提供明確的預期,告訴他們可以從叢集中獲得多少資源

我們的許多使用者對 YARN 叢集有尖銳但可預測的資源需求。例如,一個佇列可能有一組日常作業,每個作業在一天中的特定時間開始,並在相似的時間段內消耗相似數量的 CPU/MemGB。

如果我們將佇列的 MIN 設定為白天的峰值使用量,那麼叢集利用率將非常低,因為佇列的平均資源需求遠低於 MIN。

如果我們將佇列的 MAX 設定為白天的高峰用量,那麼隨著時間的推移,佇列可能會被濫用,讓資源持續接近 MAX,進而可能影響其他佇列中其他人的正常作業.

我們如何捕捉使用者的資源需求並正確設定他們的預期呢?我們提出了以下想法,稱為動態峰值(Dynamic MAX)。

動態峰值演算法使用以下設定:

  1. 將佇列的 MIN 設定為佇列的平均使用率

  2. 佇列的 MAX 設定公式如下:

Dynamic_MAX = max(MIN, MIN * 24 – Average_Usage_In_last_23_hours * 23)

複製程式碼

Dynamic_MAX 在每小時開始時計算,並應用於該小時的佇列 MAX。這裡的動態峰值演算法背後的想法是:

  1. 如果佇列在過去 23 小時內根本沒有使用,我們允許佇列峰值最多達到其 MIN 的 24 倍。這通常足以處理我們絕大多數的尖峰負載。

  2. 如果佇列在過去 23 小時內平均使用量在 MIN 水平,那麼我們只允許佇列在下一個小時的使用量不高於 MIN。有了這個規則,佇列在 24 小時內的平均使用量不會超過 MIN,從而避免了上面提到的濫用情況。

上述動態峰值演算法很容易向用戶解釋:基本上,他們的使用量最多可以飆升到他們佇列 MIN 的 24 倍,但為了公平起見,他們在 24 小時內的累積使用量不能超過 MIN 級別的叢集平均使用量。

實際上,我們將 MIN 設定為佇列平均使用量的 125%,以應對最高 25%的每日使用差異。這反過來意味著我們 YARN 叢集的平均利用率(以 CPU/MemGB 分配衡量)將在 80%左右,這對於成本效率指標來說是一個相當不錯的利用率水平。

避開高峰時間段

YARN 資源利用率的另一個問題是整個叢集級別仍然存在一種日常模式。許多團隊決定在 00:00-01:00 UTC 之間執行他們的 ETL 管道,因為據說那是最後一天的日誌準備就緒的時候。這些管道可能會執行 1-2 個小時。這讓 YARN 叢集在那些高峰時段非常忙碌。

我們計劃實現一套基於時間的費率演算法,而不是向 YARN 叢集新增更多機器,因為後者會降低平均利用率並損害成本效率。基本上,當我們計算過去 23 小時的平均使用量時,我們會應用一個根據一天中時點而變化的比例因子。例如,0-4 UTC 高峰時段的比例因子為 2 倍,其餘時間為 0.8 倍。

聯邦叢集

隨著我們的 YARN 和 HDFS 叢集不斷膨脹,我們開始注意到了一個性能瓶頸。由於叢集大小不斷增加,HDFS NameNode 和 YARN ResourceManager 都開始變慢。雖然這主要是一個可擴充套件性挑戰,但它也極大影響了我們的成本效率目標。

為了解決這個問題,擺在我們面前有兩個策略選項:

  1. 繼續提升單節點效能:比如我們可以使用配備了更多 CPU 虛擬核心和記憶體的機器。我們還可以執行棧跟蹤和火焰圖來找出效能瓶頸並一一優化。

  2. 叢集 叢集(聯邦) :我們可以建立一個由許多叢集組成的虛擬叢集。每個底層叢集都會有一個可以發揮 HDFS 和 YARN 最優效能的大小設定。上面的虛擬叢集將處理所有負載路由邏輯。

出於以下原因,我們選擇了第二個選項:

  1. 世界上大多數 HDFS 和 YARN 叢集都比我們在 Uber 需求的規模要小。如果我們執行超大叢集,很可能會遇到很多在小叢集中不會出現的未知錯誤。

  2. 為了讓 HDFS 和 YARN 能夠擴充套件到 Uber 的叢集規模,我們可能需要更改原始碼以在效能和複雜特性之間做出各種權衡。例如,我們發現容量排程器有一些複雜的邏輯會減慢任務分配的速度。但是,為擺脫這些邏輯而做的程式碼更改將無法合併到 Apache Hadoop 主幹中,因為其他公司可能需要這些複雜的特性。

為了能在不分叉的情況下利用開源 Hadoop 生態系統,我們決定構建叢集的叢集這種設定。具體來說,我們使用了基於路由的 HDFS 聯邦和 YARN 聯邦。它們都來自開源 Apache Hadoop。截至目前,我們已經建立了數十個 HDFS 叢集和少數 YARN 叢集。基於 HDFS 路由的聯邦一直是我們大資料可擴充套件性工作的基石,它也提高了成本效率。

通用負載均衡

前文介紹了 P99 和平均利用率挑戰。第 3 部分中關於廉價和大硬碟的解決方案則會涉及 IOPS P99 的重要性。

在本節中,我們將通過以下方式討論適用於 HDFS 和 YARN 的通用負載均衡方案:

  1. HDFS DataNode 磁碟空間利用率均衡 :每個 DataNode 可能都有不同的磁碟空間利用率比率。在每個 DataNode 中,每個 HDD 都可能有不同的磁碟空間利用率。所有這些都需要做均衡,以實現較高的磁碟空間平均利用率。

  2. YARN NodeManager 利用率均衡 :在任何時間點,YARN 中的每臺機器都可以有不同級別的 CPU 和 MemGB 分配和利用率。同樣,我們需要均衡分配和利用率,以實現較高的平均利用率。

上述解決方案之間有很多相似性,啟發我們提出了通用負載均衡思想,它適用於我們大資料平臺內外的更多用例,例如微服務負載均衡和主儲存負載均衡。所有這些用例之間的共同聯絡是,它們的目標都是縮小 P99 與平均值之間的差距。

查詢引擎

我們在 Uber 的大資料生態系統中使用了幾個查詢引擎:Hive-on-Spark、Spark 和 Presto。這些查詢引擎與檔案格式(Parquet 和 ORC)相結合,為我們的成本效率工作建立了一個有趣的權衡矩陣。我們使用的其他選項還包括 SparkSQL 和 Hive-on-Tez 等,它們讓我們的權衡決策變得更加複雜了。

以下是我們在提高查詢引擎成本效率方面所做的主要工作:

  1. 專注於 Parquet 檔案格式:Parquet 和 ORC 檔案格式共享一些共同的設計原則,如行組、列儲存、塊級和檔案級統計。但它們的實現是完全獨立的,並且與我們在 Uber 使用的其他專有系統具有不同的相容性級別。隨著時間的推移,我們在 Spark 中看到了更好的 Parquet 支援,在 Presto 中看到了更好的 ORC 支援。鑑於對檔案格式新特性的需求不斷增長,我們必須決定專注在一種主要的檔案格式上,於是我們最後選擇了 Parquet。單一的主要檔案格式使我們能夠將精力集中在一個單一的程式碼庫中,並隨著時間的推移積累相應的專業知識。

  1. 巢狀列修剪(Nested Column Pruning):Uber 的大資料表具有巢狀程度非常高的資料。這部分是因為我們的許多上游資料集都以 JSON 格式儲存(請參閱 設計無 Schema ),並且我們對它們強制實施了 Avro schema。於是,對巢狀列修剪的支援成為了 Uber 查詢引擎的一個關鍵特性,否則深度巢狀的資料將需要從 Parquet 檔案中完全讀出才行——即使我們只需要巢狀結構中的單個欄位.我們為 Spark 和 Presto 添加了巢狀列修剪支援。這些改進顯著提高了我們的整體查詢效能,我們還將它們回饋給了開源社群。

  1. 常見查詢模式優化:在我們的負載中看到接近一千行的 SQL 查詢的情況並不少見。雖然我們使用的查詢引擎都有一個查詢優化器,但它們並沒有針對 Uber 常見的模式有專門的優化。其中一個例子是一些 SQL 構造,如“RANK() OVER PARTITION”和“WHERE rank = 1”,其目的是提取另一列值最大的行中一列的值,也就是數學術語中的“ARGMAX”。當查詢被重寫為使用內建函式“MAX_BY”時,像 Presto 這樣的引擎可以執行得更快。

根據我們的經驗,很難預測哪個引擎最適合哪種 SQL 查詢。Hive-on-Spark 通常對於大量隨機資料有很高的可擴充套件性。反過來,對於涉及少量資料的查詢,Presto 往往非常快。我們正在積極關注開源大資料查詢引擎領域的改進,並將繼續利用它們優化我們的負載以提升成本效率。

Apache Hudi

我們在大資料平臺中遇到的最明顯的成本效益提升機會之一是高效的增量處理。我們的許多實時資料集可能會延遲到達或被更改。例如,在許多情況下,乘客直到他或她準備要求下一次行程時才會對上次行程的司機打分。信用卡的退款有時可能需要一個月的時間來處理。

如果沒有高效的增量處理框架,我們的大資料使用者必須每天掃描過去許多天的舊資料,才能讓他們的查詢結果保持新鮮度。一種更有效的方法是每天只處理增量更改,這就是 Hudi 專案的意義所在。

我們在 2016 年啟動了 Hudi專案 ,並於 2019 年將其提交給了 Apache Incubator ProjectApache Hudi 現在是一個頂級專案,且我們在 HDFS 上的大部分大資料都是 Hudi 格式。這大大降低了 Uber 的計算能力需求。

下一步計劃和待解決挑戰

大資料與線上服務同主機託管

雖然我們決定讓大資料負載在線上服務不需要自己的主機時借用後者的主機,但讓兩個負載在同一主機上執行會帶來許多額外的挑戰。

在託管對效能的影響方面有許多研究論文。我們方法的主要不同點在於,我們計劃為大資料負載提供非常低的優先順序,以儘量減少其對線上服務的影響。

融合線上和分析儲存

我們的很多資料集都儲存在線上儲存系統(無 schema 儲存在快閃記憶體上的 MySQL 資料庫中)和分析儲存系統(儲存在硬碟驅動器上的 HDFS 中的 Hive 表)中。此外,為了提供即時查詢速度,我們還投資了 Pinot 等儲存引擎。所有這些帶來了相同邏輯資料的許多副本,雖說副本是以不同的格式儲存的。

是否有可能實現一個可以同時處理線上和分析查詢的統一儲存系統呢?這將顯著降低儲存成本。

水電專案:利用維護作業來“儲存”額外的計算能力

叢集中的計算能力與電力供應很像。它通常在供應側是固定的,並且在需求激增或不一致的情況下會受到影響。

抽水蓄能水力發電 可以將多餘的電力以水的重力勢能的形式儲存起來,然後在需求高峰時將其轉換回電能。

我們可以將這種思想應用在計算能力上嗎?的確可以!這裡要介紹的一項關鍵思想是維護作業,它們是可以在第二天甚至一週內隨時發生的後臺任務。典型的維護作業包括 LSM 壓縮、壓縮、二級索引構建、資料清理、糾刪碼修復和快照維護等。幾乎所有沒有嚴格 SLA 的低優先順序作業都可以視為維護作業。

在我們的大多數系統中並沒有明確拆分維護和前臺工作。例如,我們的大資料攝取系統寫入 ZSTD 壓縮的 Parquet 檔案,這會佔用大量 CPU 資源並生成非常緊湊的檔案。換一種方式,我們還可以讓攝取系統編寫輕度壓縮的 Parquet 檔案,這些檔案佔用更多磁碟空間但 CPU 用量更少。然後我們有一個維護作業,它會稍後執行來重新壓縮檔案。通過這種方式,我們可以顯著減少前臺 CPU 的需求。

維護作業只需非保證的計算能力就能執行。正如我們之前所描述的,我們有足夠的資源用於此目的。

大資料用量的定價機制

鑑於我們用的是多租戶大資料平臺,我們經常會遇到難以滿足所有客戶資源需求的情況。我們如何優化有限硬體預算的總效用?帶有高峰時間乘數的 Dynamic_MAX 是最佳選項嗎?

我們相信實際上還有更好的解決方案。但是,這將需要提出更精細的定價機制。我們想探討的例子包括:每個團隊可以在我們的叢集上花費一種代幣,或者使用者可以用某種積分來提高他們的工作優先順序,等等。

總結

在這篇博文中,我們分享了 Uber 在提高大資料平臺成本效率方面的工作成果和理念,包括檔案格式改進、HDFS 糾刪碼、YARN 排程策略改進、負載均衡、查詢引擎和 Apache Hudi 等。這些改進顯著降低了平臺成本。此外,我們還探索了一些開放性挑戰,例如分析和線上託管以及定價機制等。然而,正如我們之前文章中概述的框架所展示的那樣,僅靠平臺效率的提升並不能確保較高的運維效率。控制資料的供應和需求也是同樣重要的,我們將在下一篇文章中討論這個主題。

宣告

Apache ®、Apache Hadoop®、Apache Kafka®、Apache Hive、Apache ORC、Apache Parquet、Apache Spark、Apache YARN、Hadoop、Kafka、Hive、ORC、Parquet、Spark、YARN 是 Apache 軟體基金會在美國和/或其他國家的註冊商標或商標。使用這些標記並不暗示得到了 Apache 軟體基金會的認可。

Presto®是 LF Projects,LLC 在美國和/或其他國家/地區的註冊商標。使用此標誌並不暗示得到了 LF Projects,LLC 的認可。

作者介紹

Zheng Shao 是 Uber 的高階工程師。他領導著整個基礎設施成本效率技術專案,重點關注大資料成本效率。他還是 Apache Hadoop PMC 成員和名譽 Apache Hive PMC 成員。

Mohammad Islam 是 Uber 的高階工程師。他共同領導資料成本效率專案,還領導資料安全和合規專案。他還是 Apache Oozie 和 Tez PMC 的成員。

原文連結: http://eng.uber.com/cost-efficient-big-data-platform/