印度最大線上食品雜貨公司Grofers的資料湖建設之路

leesf發表於2020-10-27

1. 起源

作為印度最大的線上雜貨公司的資料工程師,我們面臨的主要挑戰之一是讓資料在整個組織中的更易用。但當評估這一目標時,我們意識到資料管道頻繁出現錯誤已經導致業務團隊對資料失去信心,結果導致他們永遠無法確定哪個資料來源是正確的並且可用於分析,因此每個步驟都會諮詢資料平臺團隊,資料平臺團隊原本應該提供儘可能獨立地做出基於資料的正確決策而又不減慢速度的工具。

現代資料平臺會從許多不同的、不互連的,不同系統中收集資料,並且很容易出現資料收集問題,例如重複記錄,錯過更新等。為解決這些問題,我們對資料平臺進行了深入調研並意識到技術架構上的債務會隨著時間的推移導致大多數場景下資料的不正確,我們資料平臺的主要功能(提取,轉換和儲存)都存在問題,從而導致資料平臺存在上述質量問題。

列出的問題大致如下:

1.1 原始資料和處理後的資料缺乏分隔

5年前開始資料之旅時,我們由於缺乏遠見未將源表與派生表分開,應用表與表模式一起被轉儲到同一倉庫中。 當我們只有20張表時沒有問題,但是當超過1000張表時就會成為一個棘手的問題。

源表不僅與基於這些表構建的資料集市放在一起,而且我們通常還會對源表進行修改,結果導致資料消費者經常不確定包含在不同表中的資料的含義,並且發現很難確定將哪個表或列用作事實來源。

從工程角度來看,在故障排除過程中跟蹤資料血緣關係變得越來越困難,這使我們的MTTR很高,並經常導致終端使用者使用的中斷。由於我們將原始表與資料集市放在同一個儲存中,缺乏分離也給我們的基礎設施帶來了開銷。

1.2. 基於批處理SQL載入的侷限性

最初,我們的源表是通過對生產資料庫進行批量排程SQL生成的,這些批作業存在一些固有的問題:

  • 這些操作需要依賴一個固定的列,例如主鍵。"created_at"或"updated_at"欄位可以用作跟蹤已複製的行數d的標記,以便後續作業可以在上一個作業中斷處開始。 但是這無法複製對跟蹤列不可見的更改。例如假設使用"updated_at"列作為跟蹤器,然後在源表中刪除一行資料。
  • 在指定邊界條件(例如必須從其查詢資料的下一個標記時間)時必須非常精確。 而分析師在定義作業時通常不會考慮到這一點,並且會在邊界條件看到重複或丟失的記錄。
  • 我們的一些關鍵業務表更新頻率很高,以至於表在複製同時仍會發生變更,這會導致作業由於行衝突而失敗,但由於redshift沒有原生的合併命令,因此可能導致產生的資料與源資料不同。為了解決這個問題,我們必須在平臺中新增更多工具而使該平臺變得支離破碎,增加複雜性的同時也增加了更多的故障點。

1.3. 元件無法擴充套件

為了使資料分析人員能夠在沒有資料工程團隊任何幫助的情況下對複製操作進行程式設計,我們引入了一系列視覺化拖拽工具。這使我們在開始時可以擴大資料操作規模,但很快又變得難以處理,幾年實踐下來發現該工作從困難變成了不可能。

曾經用來執行大部分作業的工具有一個另一個缺點是當出現問題時,很少或根本無法發出警報。這導致了我們經常不知道資料是否存在問題或不一致情況,而只有在某些資料分析師提出關注時才發現。 此外隨著規模的擴充套件,這些工具開始花費的時間也越來越多。

1.4. 不支援實時資料管道

隨著組織不斷髮展,開始出現越來越多的實時場景,而通過擴充套件現有資料平臺無法滿足這些情況。

由於困擾著我們倉庫的問題太多了,我們意識到第一代資料倉儲之路已經走到盡頭,因此我們決定退後一步來考慮資料平臺到底需要什麼,如果有必要可從頭開始構建一個系統。

以下是我們列出的資料基礎架構希望具有的核心功能:

  • 克服上面列出的批處理作業的限制。
  • 儘可能分離儲存和計算,以便它們可以獨立擴充套件。
  • 減少故障點的數量。
  • 保持對變更的稽核,在發生故障時也有可以輕鬆部署的資料管道。
  • 輕鬆跟蹤系統中的更改並維護資料血緣關係以簡化故障排除步驟。

考慮到這些因素,我們最終決定構建一個使用CDC(Change Data Capture)來複制源表以構建域分離的資料湖。

2. 資料湖和CDC

首先讓我們定義"資料湖"和"資料倉儲"術語,許多組織都犯了交叉使用這些術語的錯誤,因為資料湖與資料倉儲不是同一回事,資料倉儲是儲存的資料可以被資料分析師和業務消費者輕鬆使用,另一方面資料湖是一個大型儲存系統,旨在儲存原始資料,可以在需要時通過資料倉儲進行處理和提供服務。

從工程角度來看,資料湖需要複製和儲存生產資料庫中的原始應用層資料,這意味著只要生產資料庫發生更改,資料湖就需要確保也複製該變更,有多種技術可以捕獲這些更改以便進行復制,通過重放對源所做的變更來複制資料的方法稱為"更改資料捕獲",簡稱CDC。

更改資料捕獲(CDC)可以通過三種方式完成:

  • 基於查詢
  • 基於觸發器
  • 基於日誌

前面描述的複製方法可以歸類為基於查詢的CDC,我們按計劃排程查詢來批量複製資料,此方法需要一個增量鍵來在作為拉取表中資料的標記。

基於觸發器的CDC系統使用影子表和一組觸發器來跟蹤變更,由於非常低的效能和較高的維護開銷,它是一種相對較少使用的技術。

基於日誌的CDC系統使用資料庫更改日誌,例如 Postgres中的預先寫入日誌(WAL),MySQL中的Binlog,MongoDB中的Oplog等,以在複製的資料儲存上重放變更,可以通過外掛讀取這些日誌,如Wal2Json,Postgres中的Pgoutput和Mysql中的open-replicator,基於日誌的CDC與其他方案相比有很多好處,例如捕獲了所有資料更改,包括DELETES,應用完整的事務順序使得建立實時流變得可能,因此我們在Grofers使用此技術來建立複製管道。

3. 採用增量處理的資料湖的原因

建立資料湖既昂貴又費時,在決定進行架構變更前需要確保進行徹底的評估,我們經歷了類似的過程並總結了如下幾個關注點:

3.1. 表分離有助於擴充套件性和查詢效率

資料湖是不同於資料倉儲的儲存引擎,從本質上講可以從邏輯上和物理上分離應用層的表和集市,也解決了表的特徵和血緣問題,使分析人員在進行查詢之前就已經意識到它們,萬一資料集市有不一致,團隊會進行檢查。

資料湖提供的另一個主要好處是儲存和計算的分離,其背後的原因是資料湖使用類似檔案系統儲存(例如AWS S3或傳統的HDFS)來構建,與Amazon Redshift等資料倉儲相比成本要低得多。如果查詢資料的時間小於所儲存資料的全量歷史資料,則此優勢將帶來更多的成本價值,例如我們組織中的大多數報表僅查詢過去一年的資料,但是我們在資料湖中儲存了超過5年的資料,如果將所有這些資料儲存在倉庫中,支付儲存成本會更高。

3.2.不再對源資料庫進行批量輪詢

由於我們使用資料庫日誌來為我們提供有關表上發生的所有事務的完整資訊,因此不再需要基於批處理的SQL進行資料提取,而且由於不需要標記列(例如created_at,updated_at等),因此不再有丟失資料的風險,我們還可以在整個倉庫中複製DELETE行,而通過批量查詢會丟失刪除的這些行。

3.3.支援多種應用程式資料庫引擎和業務約定

每個資料庫引擎都有其處理資料型別(例如時間,十進位制,JSON和列舉列)的特定方式,此外我們還有許多不同的後端團隊,他們在設計資料庫架構時會考慮終端使用者的需求,然而由於每個團隊根據其使用者、技術不同而遵循不同的約定,因此我們經常看到相似列的命名方式有很大的不同,資料湖可在將資料提供給分析人員之前消除精度、命名等差異。

3.4.資料損壞與Failover

任何擁有大量資料庫和系統的組織都需要在最短的時間內解決問題,資料湖允許我們做同樣的事情,在發生任何故障的情況下,我們都不必依賴源資料庫副本,由於我們可以在不影響生產系統的情況下進行重新裝載,因此對故障有了更強的防禦能力。

此外資料經歷了多個處理階段,將使得我們能夠在每個階段對應用完整性和質量進行檢查,提早告警以及發現問題。

3.5.實時分析和用例

我們採用的資料捕獲機制生成實時資料流,這些資料流可用於服務許多用例,例如監視日常操作,異常檢測,實時建議以及更多的機器學習用例。

我們不同後端系統中大約有800個表(Postgres和MySQL)在不斷更新,一天中插入與更新的比率接近60:40,如此龐大的更新量需要對大量資料進行重新處理和更新(重複資料的機率更高)。在生產環境中,很難控制查詢的質量,而由於我們無法獨立擴充套件計算能力,一些寫得不好的臨時查詢會影響到SLA,由於資料湖使我們能夠處理分割槽並基於鍵來合併行,因此它可以解決重複行的問題,然後再載入到倉庫中。此外,CDC流使我們能夠捕獲表中的每個更改,從而解決了更新遺漏的問題,因此將基於日誌的CDC與資料湖一起用作我們複製管道的體系結構似乎是我們資料平臺正確發展的下一步。

4. 與Data Lake合作的CDC工具

如前所述,我們管道中的問題不僅在於轉換和儲存層,在捕獲諸如行衝突、鎖、更新遺漏、架構變更之類的更改方面我們面臨也許多問題,因此我們必須改變從應用程式資料庫獲取資料的方式,而我們發現的解決方案是使用基於日誌的CDC,有許多基於雲和付費的工具圍繞基於日誌的資料捕獲的工具,但是由於它是體系結構中最關鍵的部分,我們決定對其進行更詳盡的調研,我們試用了AWS Data Migration Service,但由於它不在kappa架構的長期願景中(成為無法進一步複用的一對一管道),因此我們不得不尋找其他解決方案。

我們希望對該工具進行細粒度的控制以便能夠輕鬆地對監視和警報進行修改,因此不能使用黑盒服務。

我們圍繞CDC嘗試了幾個開源專案,例如Debezium,Maxwell,SpinalTap和Brooklin,其中Debezium在資料庫引擎(MySQL和Postgres)的支援、快照、列掩碼、過濾、轉換和文件支援方面表現突出,此外Debezium還擁有一個活躍的Redhat開發團隊,如果遇到無法解決問題,他們可以為我們提供及時的支援,另外我們還嘗試了Confluent源聯結器,但是其是基於查詢的CDC,所以無法繼續使用。

5. 建立一個有用的資料湖

資料湖是大量原始資料和半處理資料的儲存庫,它們的用例通常僅限於處理資料的中間層,而對業務沒有任何直接價值,此外圍繞資料湖效率低下的批評也很多,事實證明這是因為沒有挖掘資料湖價值造成的,因此我們決定不將它們作為一般的Parquet/ORC檔案進行儲存,而是新增一些直接的業務價值,例如消費者對資料湖進行主動查詢。

資料湖是一種相對較新的架構模式,有許多開源專案將後設資料新增到這些湖檔案中,這使其與倉庫非常相似,可以使用Hive,Presto和許多其他SQL進行進一步查詢。 這些專案大多數是基於以下原則:使用bloom過濾器,分割槽,索引等後設資料來進行增量更新。

  • Delta Lake:這個專案是社群中最知名的專案,由Databricks開發。它有兩種實現,一種是Databricks公司實現的商業版,另一種是開源版本,兩者雖然具有相同的體系結構,但是目前在壓縮,索引,修剪和許多其他功能方面,開源版本與Databricks商業版本差距很大,但就基於ACID的資料儲存而言,它在終端使用者工具箱中擁有最大的發展勢頭和被最廣泛的採用。

  • Apache Iceberg:最初由Netflix開發,用於儲存緩慢移動的表格資料,它具有優雅的設計,通過清單進行模式管理(模組化OLAP),但與其他兩個框架相比知名度相對較低,並且缺乏與Apache Spark或Flink等處理引擎或雲供應商的緊密整合,這使其難以採用。

  • Apache Hudi:這是Uber最初開發的開源專案,用於在DFS(HDFS或雲端儲存)上攝取和管理大檔案,它非常注重效能(如延遲和吞吐量),對不同實現如""寫時複製""和"讀時合併"進行深度優化,通常也可以將其定義為批資料的增量處理。目前,AWS生態系統已支援它(通過Redshift Spectrum)。經過POC後我們目前正在採用此方法。

我們之所以選擇Apache Hudi作為資料湖儲存引擎,主要是因為它採用了效能驅動的方法,我們的大多數表都是"寫時複製"的,因為我們不希望通過redshift提供實時更新。在使用Debezium和Kafka從源資料庫捕獲CDC之後,我們將它們放在S3中,以便它們可以通過Spark處理引擎進行增量消費,另外由於CDC原始格式無法以直接放入Hudi表中,因此我們為Nessie(源自Lake Monster)開發了一個內部工具。

6. Nessie: 湖中的怪物

Nessie是用於在處理引擎與資料湖耦合的同時提供抽象化的工具,因為CDC日誌需要被轉化為適當的格式,以便可以將其儲存為Hudi表。

在Nessie上開發的功能的如下:

  • 降低Apache Hudi與Debezium CDC整合的複雜性,例如使用來自CDC記錄的事務資訊和模式演變來生成增量鍵。
  • 支援轉儲不同間隔(分鐘/小時/天)、不同型別的原始檔案轉儲(Avro / JSON / Parquet),在MySQL和Postgres中有不同的資料型別。
  • 支援通過DeltaStreamer,Spark Streaming輪詢Kafka主題進行消費。
  • 支援生成用於進一步處理的標準銀表。
  • 支援使用標記、壓縮和其他表指標監視SLA。

7. 要考慮的點

如果打算開始使用Debezium和Hudi構建資料湖,需要考慮一些問題和侷限性,為了描述這些問題,讓我們首先了解Hudi的一些基本引數,Hudi表有如下3個重要引數,即:

  • 記錄鍵(每行的唯一ID)
  • 增量鍵(行版本的最大唯一值狀態ts)
  • 分割槽鍵(行的分割槽值)

在使用CDC資料構建資料湖時,我們面臨著圍繞增量鍵的挑戰,在增量鍵中我們必須根據LSN、Binlog Position等屬性,為Postgres和MySQL DB引擎中的每個變更/事務生成增量變更值,我們考慮過在每一行中都使用相同的修改時間,但是由於時間值的精確度和並行事務的原因,它無法解決問題。

我們還圍繞Hudi中的時間戳格式進行了自定義開發,因為其Hive整合當前不支援時間戳資料型別,另外還必須更緊密的整合倉庫(redshift)和資料湖以處理架構演變。

在Debezium中,由於缺少資料更改的情況,我們面臨著一致性方面的問題。 其中一些關鍵如下:

DBZ-2288:Postgres聯結器可能在快照流轉換期間跳過事件

DBZ-2338:複製插槽中的LSN並非單調增加

同時為了規避該問題,我們開發一個補丁指令碼,該指令碼可以跟蹤和修復丟失的更改事件,開發能夠處理具有不同版本的不同資料庫引擎的驗證指令碼也很困難,因為它們需要執行在資料湖和資料倉儲。

在整個管道的效能方面,由於Debezium遵循訊息的嚴格排序,因此我們面臨與Kafka有關將主題限制為單個分割槽的一些問題,我們通過使用增加的生產者請求大小來解決相同的問題,從而提高了記錄的整體傳輸率。

8. 總結

我們目前幾乎將所有關鍵表以及消費者都遷移到了Hudi資料湖方案上,將來我們計劃整合資料血緣和健康監控工具,同時我們也正在慢慢轉向基於流的Kappa體系結構,並且將在此基礎上構建實時系統,此外我們計劃對倉庫中基於日誌的表嘗試使用讀時合併(MOR)的Hudi表,儘管對Hudi計劃的取決於RedShift對Hudi的支援

總體而言,遷移到此架構減少了資料管道的大量波動,同時大大降低了成本。

最後感謝Grofers整個資料團隊的辛勤工作,尤其是Apoorva Aggarwal,Ashish Gambhir,Deepu T Philip,Ishank Yadav,Pragun Bhutani,Sangarshanan,Shubham Gupta,Satyam Upadhyay,Satyam Krishna,Sourav Sikka和整個團隊幫助資料分析師團隊實現了這一過渡。 另外還要感謝Debezium和Apache Hudi的開源維護者開發了這些出色的專案。

相關文章