印度最大在線食品雜貨公司Grofers的數據湖建設之路

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的開源維護者開發了這些出色的項目。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章