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 的成員。

原文鏈接: https://eng.uber.com/cost-efficient-big-data-platform/