[TOOLS] 13 分鐘閱讀OraCore 編輯部

分區選錯讓 Spark 變稅

我拆解為什麼分區設錯會默默燒掉 Spark 成本、時間與叢集健康,最後給你可直接套用的審查模板。

分享 LinkedIn
分區選錯讓 Spark 變稅

分區選錯會讓分散式工作慢、貴、又難維護,我把它拆成可直接檢查的做法。

我跑 Spark 跟數倉 pipeline 跑久了,最常遇到的不是模型不準,也不是 SQL 寫爛,而是系統看起來很忙,實際上忙得很冤。UI 顯示 executor 都在動,job 也終究會跑完,dashboard 甚至還能裝得像沒事。但成本單子很誠實,SLA 也很誠實,最後就是每個「看起來很簡單」的查詢,都變成一場小型分散式內耗。

我後來越看越覺得,問題常常不是 Spark 本身,而是 partitioning。不是那種架構圖上拿來交差的名詞,而是很土、很實際、很決定命運的東西:資料怎麼落盤、查詢掃多少、task 怎麼分、哪個 executor 先死。很多團隊會先罵引擎、罵 storage、罵網路,結果真正的元兇只是資料切法把整個系統帶歪了。

這次我拆的是 Seshendranath Balla 在 HackerNoon 的文章 The Hidden Cost of Poor Data Partitioning in Distributed Systems。我會注意到它,不是因為它講得多玄,而是它很務實地在講:分區不是附屬設定,是你每次跑 job 都在付的隱形稅。

分區不是資料表註解,是工作量控制器

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

“Poor partitioning can lead to significant performance degradation, increased resource consumption, and higher operational costs.”

分區選錯讓 Spark 變稅

翻譯一下就是:分區決定每次查詢要做多少工,也決定這些工分得公不公平。切得爛,engine 就得掃太多資料、shuffle 太多資料,或者卡在某個超大 partition 上乾等,其他 executor 看起來很忙,其實只是陪跑。

我以前踩過一個很典型的坑。某條 Spark pipeline 在紙面上看起來很乾淨,我們用了「業務上很合理」的欄位當 partition key。這種話通常就是麻煩的開頭。結果資料切出來一邊超大、一邊超小,daily job 表現像抽籤。有時快得像沒事,有時慢到像在懲罰整個團隊。儲存格式沒變,資料量也沒暴增,問題就是 layout 錯了。

這篇文章真正有用的地方,在於它把 partitioning 拉回控制面來看。它影響 scan pruning、parallelism、shuffle volume、task skew,還有 storage efficiency。你如果把它當成一次性的 schema 決策,後面每次跑 pipeline 都是在替當初那個決定繳利息。

實操上我會這樣做:先看最常見的 filter、join、access path 是什麼,再決定 partition key。使用者如果總是用 date 和 tenant 查,那就比 ER 圖上哪個欄位好看重要得多。要是你講不出這個 key 到底幫了什麼 workload,那八成不是好 key。

  • 先優化讀取模式,不要先追求命名漂亮。
  • 只要有單一值可能吃掉大量資料,就先懷疑 skew。
  • 查詢模式會變,partition 也要跟著重看。

熱分區不是例外,是瓶頸換了個名字

文章有提到 data skew,這才是很多人低估的地方。某個 partition 吃掉太多流量、太多資料、或太多相同 key 的 records,其他 partition 就算正常,整個 job 還是會被那一包拖死。分散式系統很殘酷,平均值不重要,最慢的那個 task 才決定你什麼時候下班。

所以一個 oversized partition 不只是慢而已,它會直接拉長 runtime、增加 memory pressure、提高 retry 機率,最後讓整個 pipeline 看起來像壞掉,但其實只是被一個熱點卡住。

我也看過時間分區把人騙得很慘。大家都愛按天切,直到某一天剛好是黑五、月底結帳、產品上線,某個 partition 突然變成其他天的 20 倍大。這時候你原本自以為平衡的設計,就會變成一個很貴的笑話。

白話講,partitioning 不能只靠直覺。你得先看 cardinality、頻率分布、以及資料隨時間的形狀。不然系統會很有禮貌地把不均勻藏起來,直到 production 才讓你一次看清楚。

實操上我會先抽樣 key distribution,至少看 top values、最大最小 partition size、以及 ratio。只要少數 key 吃掉大部分 rows,我就會開始想 composite partitioning、salting、bucketing,或乾脆換另一種 access strategy。

  • 不要只看 distinct count,要看頻率分布。
  • 用接近 production 的資料測,不要只拿乾淨樣本。
  • 假設某個特殊日或特殊 tenant 早晚會打爆平衡。

小分區看起來整齊,其實是在逼 scheduler 加班

分區不是只有太大會出事,太小也一樣煩。這是同一個問題的另一面,而且很常被忽略。scheduler 有成本,metadata layer 有成本,file open 也有成本。你如果切出一堆超小 partition,系統就會把時間花在管理工作,不是在做工作。

分區選錯讓 Spark 變稅

這種情況最討厭,因為團隊很容易以為 engine 很慢,實際上是自己把 overhead 做爆了。我看過 job 花超久在 launch tasks、讀 tiny files、跟 metadata 纏鬥。單一 task 都不重,所以更難抓。cluster 看起來很忙,但忙的是行政雜務,不是計算。

HackerNoon 那篇提到 operational cost,我覺得這點很準。poor partitioning 不一定會立刻炸出 outage,它更常見的是慢慢漏血。每個 job 慢一點、每次 query 多吃一點、每次 compaction 久一點,幾百次之後,雲端帳單就會替你把問題寫出來。

實操上我不會迷信「分越細越平行」這種說法。partition 數量要跟資料大小和 workload 形狀對齊。Spark 裡面 partition 太多跟太少都很煩;lakehouse table 也是,太多小檔案和太多小 partition,plan 跟 compaction 都會痛苦得要命。

我自己調 job 時,會盯著一個很土但很實用的平衡點:partition 要夠大,才能壓掉管理成本;又不能大到把 cluster 餵不飽。這件事沒有漂亮答案,只有量測。

Partition pruning 只有在你切對地方才有用

好的 partitioning 有一個很實際的好處:query engine 可以跳過不需要的資料。壞消息是,這只有在查詢 filter 跟 partition layout 對得上的時候才成立。兩邊不對齊,engine 還是得多掃很多資料,partition 再多也只是多一層儀式感。

很多團隊會自己騙自己:table 有 partition,所以一定有優化。不是。partition key 選錯的 table,只是多了管理成本的 table。如果查詢一直是 customer_id,但你卻按 ingestion date partition,pipeline 也許比較好寫,但真正的查詢不會因此變便宜。

我以前在 analytics 系統就被這個坑過。ETL 團隊愛那個 partition key,因為好寫、好落地;分析團隊則很不爽,因為每次查詢都還是掃一大堆不必要的資料。這種 hidden tax 最煩的地方在於,系統沒有壞,只是每天都差一點,差久了就是錢。

實操上我會把 top queries 跟 partition layout 一一對起來。常見 filters 如果重複出現,就應該反映在 physical design 上。若有兩種以上主流 access pattern,就別硬塞進單一 partition key,該考慮不同表設計或不同物理策略就要考慮。

  • 只有真實 filter 對得上,pruning 才會有感。
  • 把 ingest 方便跟 query 效率分開看。
  • 業務問題變了,partition 也要重審。

Cardinality 不是數學題,是踩雷清單

文章的 tag 裡有 cardinality-trap,我覺得這名字很準。cardinality 很容易讓人以為只是挑一個 distinct value 數量漂亮的欄位。高 cardinality 會帶來太多 partition、太多 metadata、太多碎片;低 cardinality 又會讓 partition 巨大、skew 嚴重、pruning 很差。兩邊都不是什麼好下場。

白話講,partitioning 不是在找一個看起來數字漂亮的欄位,而是在找一個在你的 workload 下穩定、可用、可預期的欄位。樣本裡看起來很平均的 key,上 production 可能瞬間歪掉;一個 pruning 潛力很好的 key,也可能因為寫入太頻繁而造成很糟的 write amplification。

我看過不少團隊在那邊追求「完美 cardinality」,結果反而忽略更大的問題:這個 key 有沒有穩定性?會不會隨時間變形?會不會讓流量集中在少數值?只要這三件事答案不漂亮,再好看的 spreadsheet 都救不了你。

實操上我會把候選 key 拿去看時間維度,不只看 row 數。按 hour、day、tenant、region 去看它怎麼變,再對照 workload。很多時候最好的答案不是單一 partition key,而是 composite、bucketing,甚至是不 partition。對,很多表根本不該硬切,這句我講得很認真。

我寧可跟團隊說「這張表不要 partition」,也不要看他們做出一套脆到不行的切法,然後接下來半年都在維護它。

真正的成本不是慢,是慢到沒人敢承認

這篇文章最有價值的一點,是它把 poor partitioning 從 performance 問題拉成 operational cost 問題。那包含 compute waste、storage inefficiency、job time 拉長、retry 變多,還有工程師花時間追查本來不該存在的 slowdown。

最麻煩的是,這通常不是那種會被記進 incident 的大爆炸,而是慢性出血。每個 job 只慢一點,每個 query 多吃一點資源,每次 compaction 多花一點時間。等到某一季財務問雲端帳單為什麼跳了,大家才發現原來是幾百個小問題疊成一個大洞。

我也解釋過這種帳單,真的很難看。你沒辦法指著一個 spectacular failure 說就是它。你只能指著一個設計決策,說它讓所有事情都稍微變差。這就是為什麼 partitioning 應該被當成架構層級的事,不是 implementation 細節。

實操上我會把 partition health 放進例行 data review。看 file size、partition count、skew、scan volume、job runtime。最好有人固定盯著 layout 有沒有跟 workload 脫節。只要脫節,就先改 layout,不要先加更多 compute 然後假裝問題解掉了。

  • 看 job 成本,不要只看 job 成功。
  • 追蹤 partition size 是否隨時間漂移。
  • 如果根因是 layout,優先重設計,不要只會加機器。

可抄的模板

下面這份是我會直接拿去做 design review 的版本。它的目的很簡單:把討論從「感覺上應該可以」拉回「這個 key 到底幫了什麼 workload」。你可以直接複製,改欄位名稱就能用。

# Data partitioning review template

## 1) Workload summary
- Primary readers:
- Top queries:
- Top joins:
- Write pattern:
- Freshness requirement:

## 2) Candidate partition key
- Proposed key:
- Why we chose it:
- What queries it helps:
- What writes it hurts:

## 3) Distribution check
- Distinct values:
- Top 10 values by frequency:
- Largest partition size:
- Smallest partition size:
- Skew ratio:
- Hot key risk:

## 4) Engine fit
- Partition pruning benefit:
- Shuffle reduction benefit:
- File count impact:
- Metadata overhead risk:
- Compaction needs:

## 5) Failure modes
- What happens on peak days:
- What happens with new tenants or regions:
- What happens when the access pattern changes:
- What happens if the key becomes too sparse:

## 6) Decision
- Keep as-is:
- Change key:
- Use composite partitioning:
- Use bucketing/salting:
- Do not partition:

## 7) Validation plan
- Sample dataset used:
- Query benchmark:
- Runtime baseline:
- Cost baseline:
- Recheck date:

## 8) Review checklist
- [ ] Partition key matches real filters
- [ ] No obvious hot partitions
- [ ] Partition count is manageable
- [ ] File sizes are healthy
- [ ] Query pruning is measurable
- [ ] Cost impact is documented

我會把這份模板塞進設計審查、table 建立前,還有上 production 後第一週再跑一次。partitioning 不是「設定完就不用管」的東西,它是會跟著使用方式一起變的。

原始來源是 HackerNoon 文章,作者是 Seshendranath Balla。上面這篇是我根據原文做的拆解與實務化整理,模板則是我另外整理出來的可直接套用版本,不是原文逐字搬運。

我在看這類議題時也會順手對照 Apache SparkDelta Lakedata skew,還有 PostgreSQL partitioning docs。這些連結不是拿來裝飾,是拿來把話講實在一點。