簡體   English   中英

使用 Databricks 中的 PySpark 在 Azure DataLake 中使用 partitionBy 和覆蓋策略

[英]partitionBy & overwrite strategy in an Azure DataLake using PySpark in Databricks

我在 Azure 環境中有一個簡單的 ETL 過程

blob 存儲 > 數據工廠 > 原始數據 > 數據塊 > 數據湖策划 > 數據倉庫(主 ETL)。

這個項目的數據集不是很大(大約 100 萬行 20 列給予或接受)但是我想將它們作為 Parquet 文件在我的數據湖中正確分區。

目前我運行一些簡單的邏輯來確定每個文件應該在我的湖中的哪個位置基於業務日歷。

文件模糊地看起來像這樣

Year Week Data
2019 01   XXX
2019 02   XXX

然后我將給定的文件分區為以下格式,替換存在的數據並為新數據創建新文件夾。

curated ---
           dataset --
                     Year 2019 
                              - Week 01 - file.pq + metadata
                              - Week 02 - file.pq + metadata
                              - Week 03 - file.pq + datadata #(pre existing file)

元數據是成功和自動生成的提交

為此,我在 Pyspark 2.4.3 中使用以下查詢

pyspark_dataframe.write.mode('overwrite')\
                         .partitionBy('Year','Week').parquet('\curated\dataset')

現在,如果我單獨使用此命令,它將覆蓋目標分區中的任何現有數據

所以Week 03將丟失。

使用spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")似乎可以解決問題,只覆蓋目標文件,但我想知道這是否是處理數據湖中文件的最佳方式?

我還發現很難找到有關上述功能的任何文檔。

我的第一直覺是循環遍歷單個鑲木地板並手動寫入每個分區,這雖然給了我更好的控制,但循環會很慢。

我的下一個想法是將每個分區寫入/tmp文件夾並移動每個鑲木地板文件,然后根據需要使用上面的查詢替換文件/創建文件。 然后清除/tmp文件夾,同時創建某種元數據日志。

有沒有更好的方法/方法?

任何指導將不勝感激。

這里的最終目標是為所有“精選”數據提供一個干凈安全的區域,同時擁有一個鑲木地板文件的日志,我可以將其讀入數據倉庫以進行進一步的 ETL。

我看到您在 azure 堆棧中使用數據塊。 我認為最可行和最推薦的方法是使用 databricks 中的新delta Lake 項目

它為對象存儲(如 s3 或 azure 數據湖存儲)提供了各種更新插入、合並和酸交易的選項。 它基本上提供了數據倉庫向數據湖提供的管理、安全、隔離和更新插入/合並。 對於一個管道,由於其功能和靈活性,蘋果實際上將其數據倉庫替換為僅在增量數據塊上運行。 對於您的用例和許多其他使用鑲木地板的用例,將 'parquet' 替換為 'delta'只是一個簡單的更改,以便使用其功能(如果您有數據塊)。 三角洲基本上是實木復合地板和databricks的自然進化通過提供附加的功能和以及開源它做了很多工作。

對於您的情況,我建議您嘗試使用delta 中提供的replaceWhere選項。 在進行此有針對性的更新之前,目標表的格式必須為delta

取而代之的是:

dataset.repartition(1).write.mode('overwrite')\
                         .partitionBy('Year','Week').parquet('\curataed\dataset')

https://docs.databricks.com/delta/delta-batch.html

'您可以選擇性地覆蓋分區列上謂詞匹配的數據'

你可以試試這個:

dataset.write.repartition(1)\
       .format("delta")\
       .mode("overwrite")\
       .partitionBy('Year','Week')\
       .option("replaceWhere", "Year == '2019' AND Week >='01' AND Week <='02'")\ #to avoid overwriting Week3
       .save("\curataed\dataset")

此外,如果您希望將分區設置為 1,為什么不使用coalesce(1),因為它可以避免完全洗牌。

https://mungingdata.com/delta-lake/updating-partitions-with-replacewhere/

'當您必須運行計算成本高的算法時replaceWhere特別有用,但僅限於某些分區' '

因此,我個人認為使用 replacewhere 手動指定覆蓋將更有針對性和計算效率,然后僅依賴於: spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")

Databricks 對 delta 表進行了優化,通過 bin 打包和 z 排序使其成為鑲木地板的更快、更有效的選擇(因此是自然進化):

來自鏈接: https : //docs.databricks.com/spark/latest/spark-sql/language-manual/optimize.html

  • WHERE (裝箱)

'優化匹配給定分區謂詞的行子集。 僅支持涉及分區鍵屬性的過濾器。

  • ZORDER BY

'在同一組文件中並置列信息。 Delta Lake 數據跳過算法使用共定位來顯着減少需要讀取的數據量。

  • 通過索引、統計和自動緩存支持更快地執行查詢

  • 具有豐富模式驗證和事務保證的數據可靠性

  • 簡化的數據管道,具有靈活的 UPSERT支持和單一數據源上的統一結構化流 + 批處理

您還可以查看開源項目的完整文檔: https : //docs.delta.io/latest/index.html

.. 我還想說我不為 databricks/delta 湖工作。 我剛剛看到他們的改進和功能使我在工作中受益。

更新:

問題的要點“替換現有數據並為新數據創建新文件夾”,並以高度可擴展和有效的方式進行。

在 parquet 中使用動態分區覆蓋可以完成這項工作,但是我覺得該方法的自然演變是使用增量表合並操作,這些操作基本上是為了“將來自 Spark DataFrames 的數據集成到 Delta Lake”而創建的 它們為您提供了額外的功能和優化,可以根據希望如何發生合並數據並在表上保留所有操作的日志,以便您可以在需要時回滾版本。

Delta Lake python api (用於合並): https : //docs.delta.io/latest/api/python/index.html#delta.tables.DeltaMergeBuilder

數據塊優化: https ://kb.databricks.com/delta/delta-merge-into.html#discussion

使用單個合並操作,您可以指定條件合並,在這種情況下,它可以是年份和周以及 id 的組合,然后如果記錄匹配(意味着它們存在於您的 spark 數據框和增量表中,第 1 周和第 2 周),使用 spark 數據框中的數據更新它們,並保持其他記錄不變:

#you can also add additional condition if the records match, but not required
.whenMatchedUpdateAll(condition=None)

在某些情況下,如果沒有匹配項,那么您可能想要插入並創建新的行和分區,為此您可以使用:

.whenNotMatchedInsertAll(condition=None)

您可以使用 。 converttodelta操作https://docs.delta.io/latest/api/python/index.html#delta.tables.DeltaTable.convertToDelta ,將您的鑲木地板表轉換為增量表,以便您可以使用接口。

'您現在可以將 Parquet 表就地轉換為 Delta Lake 表,而無需重寫任何數據。 這對於轉換非常大的 Parquet 表非常有用,而將其重寫為 Delta 表的成本很高。 此外,這個過程是可逆的'

您的合並案例替換存在的數據並在不存在時創建新記錄)可能如下所示:

(未測試,語法參考examples+api)

%python  
deltaTable = DeltaTable.convertToDelta(spark, "parquet.`\curataed\dataset`")

deltaTable.alias("target").merge(dataset, "target.Year= dataset.Year  AND target.Week = dataset.Week") \
  .whenMatchedUpdateAll()\
  .whenNotMatchedInsertAll()\
  .execute()

如果增量表正確分區(年,周)並且您正確使用 whenmatched 子句,則這些操作將得到高度優化,並且在您的情況下可能需要幾秒鍾。 它還為您提供一致性、原子性和數據完整性以及回滾選項。

提供的更多功能是,您可以指定要在匹配時更新的列集(如果您只需要更新某些列)。 您還可以啟用spark.conf.set("spark.databricks.optimizer.dynamicPartitionPruning","true") ,以便 delta 使用最少的目標分區來執行合並(更新、刪除、創建)。

總的來說,我認為使用這種方法是一種執行有針對性的更新的非常新穎和創新的方式,因為它可以讓您更好地控制它,同時保持操作高效。 將 parquet 與動態分區覆蓋模式一起使用也可以正常工作,但是,delta 湖功能為您的數據湖帶來無與倫比的數據質量

我的建議:我現在想說的是,對鑲木地板文件使用動態分區覆蓋模式來進行更新,您可以試驗並嘗試在一張表上使用增量合並,並使用spark.conf.set("spark.databricks.optimizer.dynamicPartitionPruning","true").whenMatchedUpdateAll()並比較兩者的性能(您的文件很小,所以我認為這不會有很大的不同)。 合並文章的 databricks 分區修剪優化於 2 月發布,因此它確實是新的,並且可能會改變產生的開銷增量合並操作的游戲規則(因為在幕后他們只是創建新文件,但分區修剪可以加快速度)

python、scala、sql 中合並示例: https : //docs.databricks.com/delta/delta-update.html#merge-examples

https://databricks.com/blog/2019/10/03/simple-reliable-upserts-and-deletes-on-delta-lake-tables-using-python-apis.html

我們可以使用帶有附加的saveAsTable並在此之前刪除分區,而不是直接寫入表。

dataset.repartition(1).write.mode('append')\
                     .partitionBy('Year','Week').saveAsTable("tablename")

用於刪除以前的分區

partitions = [ (x["Year"], x["Week"]) for x in dataset.select("Year", "Week").distinct().collect()]
for year, week in partitions:
    spark.sql('ALTER TABLE tablename DROP IF EXISTS PARTITION (Year = "'+year+'",Week = "'+week+'")')

如果我在您的方法中遺漏了一些至關重要的內容,請糾正我,但您似乎想在現有數據之上編寫新數據,這通常是用

write.mode('append')

而不是'overwrite'

如果你想把數據按批次分開,所以你可以選擇它上傳到數據倉庫或審計,除了將此信息包含在數據集中並在保存時對其進行分區之外,沒有其他明智的方法,例如

dataset.write.mode('append')\
                     .partitionBy('Year','Week', 'BatchTimeStamp').parquet('curated\dataset')

對 Parquet 文件格式的任何其他手動干預充其量都是 hacky,最壞的風險是使您的管道不可靠或損壞您的數據。

Mohammad 提到的 Delta 湖總體上也是一個很好的建議,可以將數據可靠地存儲在數據湖中,並且是目前的黃金行業標准。 對於您的特定用例,您可以使用其進行歷史查詢的功能(附加所有內容,然后查詢當前數據集和上一批之后的差異),但是審計日志在時間上受到您如何配置 delta 湖的限制,並且可以低至7天,所以如果你想長期獲得完整的信息,無論如何你都需要遵循保存批次信息的方法。

在更具戰略性的層面上,當遵循原始 -> 策展 -> DW 時,您還可以考慮添加另一個“跳躍”並將准備好的數據放入“預處理”文件夾中,按批次組織,然后將其附加到策展和 DW 集.

附帶說明一下, .repartition(1)在使用鑲木地板時沒有太大意義,因為鑲木地板無論如何都是多文件格式,因此這樣做的唯一影響是對性能的負面影響。 但是,如果您使用它有特定的原因,請告訴我。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM