支撐尺寸調整、最佳化和故障排除的核心概念。 如果您是 Fabric 中的 Spark 新手,請先閱讀此內容。
一般注意事項
案例:您是 Spark 的新手。 注意事項和禁忌是什麼
| 用例 | 最佳做法 |
|---|---|
| 使用最佳化序列化格式 | 建議:偏好 Avro、Parquet 或最佳化資料列列 (ORC) 等格式,因為它們會內嵌結構描述、結構緊湊,並最佳化儲存和處理。 在 Fabric 中,使用 Delta 格式來取得原子性、一致性、隔離性、持久性(ACID)保證和效能優勢 |
| 謹慎使用 XML/JSON | 不要依賴大型 JavaScript 物件表示法 (JSON) 或可延伸標記語言 (XML) 檔案的結構描述推斷,因為 Spark 會讀取整個資料集來推斷結構描述,這會減慢處理速度並大量消耗記憶體。 讀取 JSON/XML 時提供靜態主要結構描述,或用於 .option("samplingRatio", 0.1) 加速讀取,但請注意,如果範例不代表完整資料集,讀取可能會失敗。 更安全的方法是從代表性範例推斷模式,並針對所有讀取持續保存。避免剖析大型 XML 檔案。 由於標籤處理和類型轉換,XML 解析的執行速度本質上較慢。 |
| 最佳化聯結和篩選 | 執行:在聯結之前,套用欄修剪和列層級過濾,以減少重組和記憶體使用。 當您使用DataFrame APIs時,Catalyst優化程式會自動處理述詞下推。 避免使用彈性分散式資料集(RDD)API,因為它們會繞過Catalyst最佳化。 |
| 偏好 DataFrames 而不是 RDD | 請使用 DataFrames 而不是 RDD 來進行大多數操作。 DataFrames 使用 Catalyst 優化器和 Tungsten 執行引擎來實現高效執行。 |
| 啟用調適性查詢執行 (AQE) | 建議:開啟 AQE 以動態最佳化隨機分割區並自動處理傾斜的資料。 |
執行器記憶體管理
實務範例:您想要瞭解執行程式記憶體管理以進行效能調整。
即使執行程式設定了 56 GB 記憶體,Spark 也不允許將所有記憶體直接用於使用者資料。 Spark Core 劃分和管理執行器記憶體:
保留記憶體: 保留給系統和 Spark 內部額外負荷的固定部分 (例如 Java 虛擬機器 (JVM)、內部)。
用戶內存: 儲存使用者定義函數 (UDF)、局部變數、資料結構 (清單、地圖、字典) 和計算期間建立的物件。
儲存記憶體: 儲存可快取的資料、廣播變數和可快取的混洗資料。
執行記憶體: 用於中間計算(資料混排、連接、排序、聚合)。
動態記憶共享: 儲存體和執行記憶體之間的界限是可移動的。 Spark 可以將記憶體從一個區域借用到另一個區域,從而允許靈活的記憶體使用。
溢出: 當儲存記憶體或執行記憶體需求超過總記憶體減去借用的部分後的可用記憶體時發生。 這會強制資料進入磁碟,這可能會影響效能。
記憶體不足 (OOM) 錯誤
案例:Spark 作業失敗,並發生記憶體不足 (OOM) 錯誤。
驅動程式 OOM:
當 Spark 驅動程式超過其配置的記憶體時,就會發生驅動程式 OOM 錯誤。
常見原因:驅動程式密集的作業,例如 collect()、countByKey(),或大型 toPandas() 呼叫,將太多資料提取到驅動程式記憶體中。
緩解措施:盡可能避免駕駛員繁重的操作。 如果不可避免,請增加驅動程式大小和基準測試以找到最佳配置。
執行器記憶體不足 (OOM):
當 Spark 執行程式超過其配置的記憶體時,就會發生執行程式 OOM 錯誤。
常見原因:在大型資料集上進行記憶體和計算密集型轉換(例如,廣泛連結、彙總、洗牌)或快取/保存的資料集超出了執行程式的可用記憶體(執行 + 儲存區域)。
緩解措施:如有必要,增加執行器記憶體,調整 Spark 記憶體分數 (spark.memory.fraction、spark.memory.storageFraction) 並選擇性地保存。 確保快取資料適合可用記憶體。
資料偏差
偏斜的症狀:
- 在 Spark UI 中,一些任務比其他任務花費的時間更長(階段任務顯示沉重的尾巴)。
- 階段指標中中位數和最大任務時間之間的差距很大。
- 具有針對幾個分割區的大型隨機讀取或寫入操作的階段。
常見原因:
- 加入/群組金鑰(熱鍵)的資料分佈不均。
- 資料磁碟區的分割區不正確或分割區太少。
- 造成大型記錄或許多為 null 或空的索引鍵的上游資料異常。
緩解措施:
- 重新分割或合併以提高分區並行處理能力並平衡分區大小。
- 套用金鑰加鹽或自訂分區來將熱門鍵分散到不同分區。
- 使用 AQE(自適應查詢執行)來合併重分區後的分割區並啟用 Skew Join 優化。
- 對小型查詢表使用廣播連接,以完全避免資料重組。
- 在昂貴計算階段之前,保存平衡的中間數據集,然後重新執行作業。
UDF 最佳實務
案例:您需要套用無法透過內建 DataFrame 函式表示的自訂邏輯。
盡可能使用 Spark DataFrame API。 Catalyst 最佳化工具會最佳化內建函數,並在 JVM 上原生執行它們,因此可提供最佳效能。
如果您必須使用 UDF (使用者定義函式),請避免使用一般 PySpark Python UDF。 相反,請考慮以下替代方案:
Pandas UDF(也稱為向量化 UDF):使用 Apache Arrow 在 JVM 和 Python 之間進行高效的資料傳輸。 Pandas UDF 允許向量化操作,與逐行 Python UDF 相比顯著提高了效能。
Scala/Java UDF:直接在 JVM 上運行,避免 Python 序列化開銷。 Scala/Java UDF 通常優於 Python UDF。
請謹慎使用 Python UDF。 每個執行器都會啟動一個單獨的 Python 進程,需要在 JVM 和 Python 之間對資料進行序列化和反序列化。 這會造成效能瓶頸,尤其是在大規模時。
錯誤記錄
案例:Fabric Spark 中錯誤記錄的最佳做法
使用
log4j而不是print()給駕駛員帶來沉重的負擔。 使用log4j,您可以存取驅動程式記錄中的記錄並搜尋它們 (使用記錄器名稱,例如:PySparkLogger)。將讀取、寫入和轉換包裝在 try 和 except 區塊中。 使用
logger.error處理例外狀況,使用logger.info顯示進度訊息。Python 日誌記錄: 非常適合記錄作業、狀態更新,或從僅在 Spark 驅動程式上執行的程式碼偵錯資訊。 Python 的日誌記錄模組不會傳播到執行器日誌。 請參閱 開發、執行和管理筆記本文件。
Spark 的 log4j: 在 Spark 中強大的生產級應用程式日誌記錄標準,因為它能夠與 Spark 的驅動程式/執行器日誌進行原生整合。
PySpark 中的 log4j 使用範例:
import traceback # Get log4j logger log4jLogger = spark._jvm.org.apache.log4j logger = log4jLogger.LogManager.getLogger("PySparkLogger") logger.info("Application started.") try: # Create DataFrame with 20 records data = [(f"Name{i}", i) for i in range(1, 21)] # 20 records df = spark.createDataFrame(data, ["name", "age"]) logger.info("DataFrame created successfully with 20 records.") df.show(s) # 's' is not defined -> will throw error but the application will not fail except Exception as e: logger.error(f"Error while creating or showing DataFrame: {str(e)}\n{traceback.format_exc()}")集中錯誤監控:
在環境中使用診斷發送器擴充模組(使用 Azure Log Analytics 監視 Apache Spark 應用程式),並將其連結至執行 Spark 應用程式的筆記本。 發射器可以將事件記錄、自訂記錄 (例如 log4j) 和計量傳送至 Azure Log Analytics/Azure 儲存體/Azure 事件中樞。 將 log4j 名稱傳遞至屬性:
spark.synapse.diagnostic.emitter.\<destination\>.filter.loggerName.match。為了進行偵錯,您也可以將失敗的資料列/記錄收集到 Lakehouse (LH) 資料表,以擷取記錄層級的錯誤資料。