Spark SQL 診斷
何時使用
用戶提供以下任意組合時使用:
- 一個 Spark UI / History Server / YARN proxy 位址;
- 一條 Spark SQL 和它的
EXPLAIN FORMATTED; - Kyuubi / Spark Driver 運行日誌;
/api/v1/...匯出的 JSON,或 event log。
目標不是「看幾個指標」,而是回答:
- 這次查詢慢在哪一層:掃描、shuffle、join、聚合、spill、GC、排程、冷啟動;
- 有沒有資料傾斜,傾斜在哪個 stage / task / SQL 片段;
- 哪些是資料問題,哪些是資源或配置問題;
- 下一步應該改 SQL、改配置,還是補資料。
輸入優先級
| 輸入 | 能做什麼 |
|---|---|
| UI 位址 + Execution ID/提交時間 + SQL | 最完整,task 級診斷 |
| 運行日誌(含 app ID 和 UI 位址)+ SQL | 先解析日誌,再存取 UI;UI 不可用時降級為 stage 級 |
| EXPLAIN FORMATTED | 只能做計劃結構和 SQL 片段映射 |
| event log / History Server | UI 過期後的兜底 |
日誌裡通常已經包含:
application ID: application_xxx
application web UI: http://host:8088/proxy/application_xxx
所以不需要用戶再單獨貼一遍 UI 位址,但必須實際探測位址是否可存取。
工作流程
1. 收集並解析輸入
- 從日誌提取 app ID、UI 位址、operation ID、stage/job 時間線、Final Plan;
- 從 SQL 原文提取表名、join 條件、group by / distinct / window / order by;
- 從
EXPLAIN FORMATTED提取運算子、輸入輸出列、join 條件、聚合 key。
2. 定位實際執行
- 優先使用 Execution ID;
- 否則按 SQL 文本 + 提交時間在
/sql列表中匹配; /sql預設只返回從 offset=0 開始的少量記錄,必須分頁或按 ID 探測;- 多條候選時,選擇時間最近且描述匹配的一條;仍不唯一就問用戶。
3. 拉取運行資料
執行腳本(推薦)。下面的 scripts/ 相對於本 skill 目錄;如果在其他目錄執行,請先 cd 到 skill 目錄,或使用絕對路徑。
python scripts/spark_diagnose.py \
--url "http://host:8088/proxy/application_xxx" \
--exec-id 94 \
--sql-file query.sql \
--explain explain.txt \
--log run.log \
--out report.md \
--json report.json
如果不確定 Execution ID:
python scripts/spark_diagnose.py \
--url "http://host:18080/history/application_xxx/" \
--sql-file query.sql \
--exec-time "2026-11-02T08:06:50Z" \
--out report.md
鑑權參數:
--cookie "name=value; name2=value2"
--header "X-Token: ..."
--user "user:password"
--kerberos # 使用 curl --negotiate
--insecure # 跳過 TLS 校驗,僅限內網自簽憑證
唯讀原則:腳本只呼叫 GET,不執行 SQL,不寫叢集。
4. 解讀指標
腳本會輸出結構化 JSON 和 Markdown。重點看:
- Stage 時間線:牆鐘 vs
executorRunTimevs task 啟動延遲; - task 分佈:duration、records、shuffle bytes 的 max/median/p95;
- 真傾斜:一個 task 的 records 和 duration 同時顯著高於中位數;
- 假長尾:task 記錄數正常,但
executorDeserializeTime很高,通常是冷 executor; - spill:
memoryBytesSpilled/diskBytesSpilled,以及計劃節點的spill size; - 計劃節點:Scan、Exchange、Join、Aggregate 的 output rows / shuffle bytes / duration;
- 配置:AQE、skewJoin 閾值、shuffle 分區數、廣播閾值、動態分配。
5. 撰寫最終報告
腳本給的是證據和候選結論。最終回覆必須結合 SQL 語義補充:
- 運算子 → SQL 片段映射;
- 熱點 key 的業務含義(如果能從 SQL/建表邏輯推斷);
- 建議的優先級和預期效益;
- 明確標註「需要外部驗證」的部分。
報告結構:
- 結論先行(按影響排序);
- 執行概覽與時間線;
- 傾斜/長尾證據表;
- SQL 計劃映射;
- 配置與風險;
- 建議;
- 證據來源與侷限;
- 仍需外部驗證的問題。
關鍵判斷規則
這些規則必須遵守,避免最常見的誤判:
-
傾斜看 records 和 duration,不能只看 shuffle bytes。 高度可壓縮的熱點 key(例如大量
item_id=1)可能只佔幾 MB,但記錄數佔 60%。 -
區分真傾斜和冷啟動。 慢 task 如果記錄數正常、
executorDeserializeTime / duration很高,是冷 executor,不是資料傾斜。 -
partial aggregation 會掩蓋 GROUP BY 傾斜。 計劃裡有
partial_count/partial_sum時,熱點 key 會在 shuffle 前被壓成少量行;要檢查是不是聚合寫法本身就規避了傾斜。 -
count(DISTINCT)是兩階段去重。 第一次 shuffle 的 key 通常包含 distinct 字段(如(item_id, buyer_id)),熱點會被攤開;不要按普通 GROUP BY 判斷。 -
AQE skew join 是 size-based。 判斷依據是壓縮後的分區大小與
skewedPartitionFactor/skewedPartitionThresholdInBytes;可壓縮熱點可能低於閾值,不會被 split。 -
增加 shuffle 分區數不能稀釋單個 hot key。 單個 key 永遠進一個分區;分區越多,其他分區越小,max/median 反而越大。
-
Stage 牆鐘遠大於 task 運行時間時,先查排程。 看
launchTime - submissionTime、動態分配minExecutors、YARN 佇列。 -
join 輸出行數明顯大於輸入行數時,才懷疑膨脹。 輸出行數等於事實表行數時,問題通常是分佈,不是重複 key。
邊界
- 看不到真實資料內容;熱點 key 的具體值需要用戶查資料;
- YARN 佇列排隊、容器分配慢的具體原因需要 RM/Kyuubi 日誌;
- UDF/自訂程式碼問題需要程式碼和 executor 日誌;
- 沒有 UI/event log 時,普通 driver 日誌只能做 stage 級診斷;
- 不執行 SQL、不修改資料、不列印 cookie/token。
參考
references/endpoints.md:REST 端點與分頁;references/metrics.md:字段差異與單位換算;references/heuristics.md:閾值、計劃和傾斜判定。