返回列表

AWS帳號購買 AWS EMR 叢集 Spark Job 執行失敗:S3 最終一致性与 HDFS 空間爆滿排查

亞馬遜雲AWS / 2026-08-04 16:16:06

問題表象:Spark Job 看似隨機失敗,其實有跡可循

在 AWS EMR 上跑 Spark,最讓人頭痛的不是明確報錯,而是那種看起來像「偶發」的失敗:同一個 Job,早上跑得過,下午忽然掛掉;重試一次又好了;換一批資料又開始失敗。若只看表面,很容易把問題怪到程式邏輯、資料格式,甚至誤以為是叢集不穩。但實際上,很多失敗都與儲存層有關,尤其是 S3 的讀寫時序,以及 HDFS 磁碟空間被吃滿。

這類問題最麻煩的地方在於,它們常常不是單點故障,而是兩個風險疊在一起:一邊是任務大量產生暫存檔、shuffle 檔或輸出檔,讓 HDFS 壓力暴增;另一邊是 Spark 讀寫 S3 時,假設某些檔案已經可見,但實際上還沒完全同步,於是出現找不到檔案、讀到半成品、重複提交等狀況。只要其中一環出問題,整個 Job 就可能失敗。

常見的失敗訊號

如果你在日誌裡看到下面這幾種訊號,就值得優先往 S3 或 HDFS 查:

  • 讀取 S3 時出現 NoSuchKey、FileNotFound、Cannot locate split 之類錯誤。
  • 輸出到 S3 後,後續步驟立刻讀取,卻偶爾找不到剛寫完的檔案。
  • 任務在 shuffle 階段卡住,最後拋出磁碟空間不足、No space left on device。
  • YARN container 反覆失敗,stderr 出現本機暫存目錄無法寫入。
  • 同一個 Job 前幾次成功,資料量增加後開始大量失敗。

如果你只在 Spark UI 看執行時間,可能會誤以為是 executor 效能不夠;但若把 driver、executor、YARN、HDFS、S3 這幾層日誌串起來看,通常很快就能找到根因。

先分清楚:問題是在計算,還是在儲存

排查的第一步,不是急著改程式,而是先問自己一個問題:失敗發生在資料讀寫之前,還是 shuffle、輸出、後處理階段?如果是資料一開始就讀不到,多半是路徑、權限、S3 可見性或檔案清單問題。如果是執行到一半才掛掉,而且每次都在資料量大的分區出事,則更像是 HDFS 或本機磁碟空間不足。

這個區分非常重要,因為兩者的解法完全不同。前者需要你檢查輸入資料是否真正存在、分區是否寫完、清單是否同步、是否有延遲可見;後者則要看磁碟使用率、臨時目錄、shuffle 檔、spill 檔、以及 EMR 節點規格是否撐得住工作負載。

先看哪裡的日誌

實務上,建議先按這個順序看:

  1. Spark driver log:先看 Job 失敗的第一個異常點,不要只看最後一層包裝錯誤。
  2. AWS帳號購買 Executor log:確認是否所有 executor 都同時出錯,還是只有少數節點失敗。
  3. YARN container log:檢查是不是容器被殺掉、磁碟寫入失敗、記憶體或本機空間不足。
  4. HDFS 使用率與節點狀態:看 NameNode、DataNode 是否告警,空間是否接近滿載。
  5. AWS帳號購買 S3 存取痕跡:確認檔案是否已經寫入、列舉是否完整、是否存在重試與延遲。

很多人習慣先改 Spark 參數,像是調大記憶體、增加 shuffle partitions、降低並行度,但如果底層是 HDFS 爆滿,這些調整只能延緩失敗,不能真正解決問題。

S3 的一致性問題,為什麼會讓 Spark 看起來像壞掉

過去大家常把 S3 視為最終一致性儲存,這代表你剛寫入的物件,不一定能立刻被 list 或 read 到。雖然現在 S3 對多數操作已經提供強一致性,但在一些舊架構、跨帳號同步、跨區複製、外部同步工具、或沿用舊有假設的 EMR 作業裡,仍然可能出現「寫完了卻暫時看不到」的情況。尤其當 Spark 的上游和下游緊接在一起,這種問題就特別致命。

舉個簡單例子:第一個 Job 把資料分批寫到 S3 的某個資料夾,第二個 Job 立刻用該資料夾做輸入。若第二個 Job 讀的是目錄清單,而清單還沒完全同步,它就會認定輸入不完整,進而漏讀資料或直接失敗。這種錯誤最可怕的地方在於,它不是每次都重現。資料少的時候沒事,資料一多、分片一多、併發一高,問題才浮出來。

哪些情境特別容易踩雷

  • 寫入後立刻做下游讀取,沒有等待檢查點或完成訊號。
  • 使用資料夾清單當作輸入來源,卻假設列舉結果一定完全即時。
  • 多個 Spark Job 同時寫同一個 S3 路徑,彼此覆蓋或互相干擾。
  • 把中間結果直接放在 S3,沒有使用原子提交策略。
  • 用舊版自訂程式或舊依賴套件,仍沿用過去對 S3 的不安全假設。

要避免這類問題,核心不是一味等待,而是建立可驗證的完成條件。比如寫入完成後,先落一個 _SUCCESS 標記,再由下游任務確認標記存在才開始讀取;或者把中間資料先寫到暫存路徑,完成後再切換到正式路徑。這樣就算底層有延遲,也不會讓下游直接踩到半成品。

HDFS 空間爆滿:真正拖垮 EMR 的常是這一刀

如果說 S3 的問題容易讓 Job 讀錯資料,那 HDFS 空間爆滿則常常直接讓 Job 失敗。很多人一看到 HDFS 滿了,第一反應是「不是還有 S3 嗎」,但 Spark 在執行時,並不只把資料放在 S3。shuffle、排序、join、aggregation、broadcast 溢出、臨時檔、stage 快照、spill 檔,很多都會落到本機磁碟或 HDFS 相關掛載空間。當空間被吃光,作業不一定立刻大爆炸,常常是先變慢,再開始重試,最後才整批失敗。

在 EMR 上,這種情況尤其常見。因為叢集是可彈性擴縮的,很多團隊會把它當成一次性運算平台,忽略節點磁碟配置。資料量小的時候,問題不明顯;等到某天上游資料翻倍,或者 join key 分佈不均,少數節點就可能被大量 hot partition 壓垮,導致某幾台機器的磁碟先滿,整個 Job 跟著倒下。

不是只有容量滿,還要看小檔案與 inode

有些人會說磁碟明明還有空間,為什麼還是寫不進去?這通常跟兩個因素有關。第一是小檔案太多,雖然總容量沒滿,但檔案數量暴增,管理成本和 metadata 壓力變高;第二是 inode 或臨時目錄被耗盡。對 Spark 來說,很多任務產生的中間檔非常碎,尤其是大量 partition、傾斜資料、頻繁 shuffle 時,這個問題更明顯。

因此,排查 HDFS 不是只看整體容量百分比。你還要看:哪些節點最滿、哪些目錄長得最快、是否有孤兒暫存檔、是否有舊輸出沒清、是否某個應用反覆留下失敗碎片。很多時候,真正拖垮叢集的不是大資料本身,而是沒有被清掉的中間產物。

實際排查時,建議照這個順序走

  1. 確認失敗的第一個錯誤訊息,不要只看最終 Job Failed。
  2. 判斷錯誤屬於讀取、寫入、shuffle、還是後處理。
  3. 檢查 S3 路徑是否有新舊資料混用、是否有完成標記、是否有多任務同寫。
  4. 查看 EMR 節點磁碟與 HDFS 使用率,特別是失敗當下的熱點節點。
  5. 檢查 /tmp、/mnt、/emr、Spark local dir、YARN local dir 是否已經接近滿載。
  6. 比對成功與失敗批次的資料量、分區數、join 鍵分佈與 shuffle 大小。

這個順序之所以有效,是因為它先找變化最大的地方,再往下挖。若你先去改資源規格,可能會花掉很多時間;但如果先確認是某一個分區特別大、某一個臨時目錄沒清、或某個下游提前讀取,問題很快就會浮現。

修復策略:分成 S3、HDFS、Spark 三層處理

S3 層的做法

對 S3 相關問題,重點是把「資料是否完成」和「資料是否存在」分開。不要讓下游直接掃描一個正在被寫入的資料夾。更穩妥的做法是:先寫入 staging 路徑,待任務成功後再移轉或切換到正式路徑;或由上游產生明確的完成旗標,下游只認旗標,不直接猜測清單是否已就緒。若你的架構中仍有舊式等待邏輯,也要確認是否還需要 EMRFS 的一致性輔助設定,避免因舊假設造成誤判。

AWS帳號購買 另外,要避免多個流程共用同一輸出路徑。S3 雖然不會像檔案系統那樣被鎖住,但路徑共用會讓清理、覆蓋、重跑與排程互相打架。對批次作業來說,輸出路徑最好帶上日期、批次號或執行 ID,讓每次寫入都有自己的空間。

HDFS 層的做法

對 HDFS 或本機磁碟問題,第一步是把垃圾清掉。包括失敗任務殘留的暫存檔、過期的 shuffle 資料、未完成的輸出目錄、以及不再需要的中間結果。接著再看節點規格是否匹配工作負載。如果 Job 本身就是大規模排序或高比例 join,卻只給很小的磁碟與本機盤,失敗只是遲早的事。

在配置上,可以從三個方向減壓:降低單次處理資料量、增加可用磁碟空間、以及減少本機落盤。前者靠合理分批和分區,後者靠節點規格與 EBS 設計,第三個則靠 Spark 參數和資料模型調整。不要把所有問題都丟給更大的機器,因為資料傾斜和小檔案問題,就算換更大節點也可能照樣爆。

Spark 層的做法

Spark 本身也能幫你減少磁碟壓力。像是適當調整 partition 數量,避免單個 partition 過大;減少不必要的 wide transformation;對重複使用的中間結果考慮 cache,但要注意 cache 也是記憶體與磁碟的消耗;對容易傾斜的 join,採用 salting、broadcast join 或先做預聚合。若發現 executor 常因本機暫存目錄爆滿而失敗,應檢查 spark.local.dir、YARN 本機目錄與節點磁碟配置是否一致。

還有一個常被忽略的點是失敗重試。重試不是萬靈丹。如果失敗原因是資料沒寫完,重試也許會自動成功;但如果失敗原因是 HDFS 滿了,重試只會把同樣的壓力再跑一次,甚至加速把剩餘空間耗盡。真正有效的重試,應該建立在你已確認根因可消除的前提下。

把排查流程制度化,才不會每次都重新猜

很多團隊的痛點不在於不會修,而在於每次出事都從零開始。今天是 S3,明天是 HDFS,後天又變成 executor memory。其實只要建立固定流程,排查速度會快很多。比如每次 Job 失敗,都強制記錄這幾件事:輸入路徑、輸出路徑、批次號、資料量、executor 數量、失敗時間、第一個異常點、當時磁碟使用率、以及是否有下游提前讀取。這些資訊一旦完整,後面很多判斷都能直接縮小範圍。

AWS帳號購買 你也可以把經驗變成監控。當 S3 輸出完成但下游尚未開始時,要有明確標記;當 HDFS 使用率高於某個門檻時,要主動告警;當某個 EMR 節點磁碟持續上升時,要能立刻看到。比起事後救火,這些訊號更能避免批次作業在半夜掛掉。

結語:別急著怪 Spark,先看儲存層

EMR 上的 Spark Job 失敗,表面上像是計算引擎不穩,實際上常常是資料可見性與磁碟資源在背後作怪。S3 相關問題會讓下游誤讀半成品,HDFS 空間爆滿則會直接把 shuffle 與暫存流程拖死。真正成熟的排查方式,不是靠猜,而是把問題切開:先看錯誤屬於哪一層,再看資料是否已完成,最後檢查磁碟與中間檔是否失控。

只要你把這套思路固定下來,很多看似玄學的失敗都會變得很直白。該補的是完成標記,就不要亂調記憶體;該清的是臨時檔,就不要只改重試次數。當排查順序正確,Spark 不會那麼難搞,EMR 也不再是黑盒子。

Telegram售前客服
客服ID
@cloudcup
联系
Telegram售后客服
客服ID
@yanhuacloud
联系