第6章 トラブルシューティング・監視・最適化 / 想定学習時間:30〜40分 / 最終確認:2026年8月

6-2. Spark UIでのボトルネック診断(データスキュー・シャッフル・ディスクスピル)

🎯 この節の学習目標

1. Spark UI の階層:Jobs → Stages → Tasks

6-1 で「どのタスクが遅いか」まで絞り込んだら、次はそのタスク(ノートブックやジョブの処理)の内部を Spark UI で覗きます。Spark UI はクラスタの詳細画面や、ジョブ実行の詳細から開けます。Spark の実行は次の3階層で構成され、Spark UI もこの階層に沿ってタブが分かれています。

階層単位Spark UI で見るもの
Jobアクション(writecount など)1つに対応する実行単位どの処理(アクション)が時間を使っているかの全体像
StageJob をシャッフル境界で区切った単位。wide 変換(join / groupBy など)のたびに新しいステージができる診断の主戦場。ステージごとの所要時間、shuffle read/write 量、spill 量
TaskStage 内でパーティション1つを処理する最小単位。並列実行されるタスク間のばらつき(Min/Median/Max)。スキュー検出の決め手

ボトルネック診断の基本手順は、「Jobs タブで最も時間のかかっている Job を見つける → その中で最も遅い Stage を開く → Stage 内のタスクメトリクスの分布を読む」という流れです。

2. ステージのメトリクスを読む:Summary Metrics の分布がすべて

ステージの詳細画面には、そのステージの全タスクについての Summary Metrics(要約統計)が表示されます。ここで注目すべきは個々の値ではなく分布、特に Min / Median(中央値)/ Max の関係です。

メトリクス意味異常のサイン
Duration(タスク実行時間)各タスクの処理時間Median は短いのに Max だけ突出 → スキューの疑い
Shuffle Read / Writeシャッフルで読み書きしたデータ量Read の Max が Median の数倍〜数十倍 → 特定パーティションへのデータ集中(スキュー)。全体の量自体が巨大 → シャッフル過多
Spill (Memory / Disk)メモリに収まらずディスクへ退避したデータ量0 でない値が出ていること自体が黄信号。大きいほど深刻
Input / Outputステージが読んだ/書いたデータ量想定よりはるかに大きい → フィルタやパーティションプルーニングが効いていない

📝 試験のポイント

試験では「あるステージのタスクメトリクスがこうなっている。原因は何か/まず確認すべきことは何か」という形式で問われます。判別の軸はただ1つ、「タスク間で均等か、偏っているか」です。偏っている(Max だけ突出)ならスキュー全タスク一様に重い(shuffle 量や spill が全体的に大きい)ならシャッフル過多・スピル、と読み分けます。

3. 3大ボトルネックの診断と対策

3-1. データスキュー:1つのタスクだけが極端に遅い

症状:ステージの完了が異常に遅い。進捗が「199/200 タスク完了」のような状態で長時間止まる。
Spark UI での確認箇所:ステージの Summary Metrics で、task duration と shuffle read の Max が Median から大きく乖離していないかを見ます。大半のタスクは数秒〜数分で終わるのに、1〜数個のタスクだけが桁違いに遅い・読み込み量が大きいのが典型パターンです。
原因:結合キーや集計キーの値の分布が偏っており(例:「不明」「NULL」に大量の行が集中)、シャッフル時に特定パーティションへデータが集中している。
対策:

💡 具体例(公式サンプル問題の型):Min/Median 400MB、Max 5GB

あるジョブの特定ステージで、タスクあたりの shuffle read が Min と Median は約400MBなのに、Max だけが5GBに達しているとします。この「中央値は正常、最大値だけ突出」というパターンはデータスキューの典型的な特徴です。この場合にまず取るべき行動は、AQE が有効で、そのスキュー結合処理(skew join handling)が働く状態になっているかを確認することです。クラスタ全体のメモリ増強やノード追加は、偏り自体を解消しないため根本対策になりません。全パーティション数を増やしても、偏ったキーのデータは結局同じパーティションに集まるため効果は限定的です。

3-2. シャッフル過多:wide 変換がデータを動かしすぎている

症状:ステージ間の待ち時間が長く、ジョブ全体がネットワークI/Oに支配される。
Spark UI での確認箇所:ステージ一覧で Shuffle Read / Shuffle Write の合計量を確認します。入力データに対してシャッフル量が不釣り合いに大きい、シャッフルを伴うステージが多段に連なっている、といった形で現れます。
原因:join・groupBy・distinct などの wide 変換は、キーごとにデータをノード間で再配置(シャッフル)します。不要な結合の繰り返し、結合前の絞り込み不足、パーティション数の不適合が典型要因です。
対策:

3-3. ディスクスピル:メモリに収まらずディスクへ退避している

症状:タスクは失敗しないが全体的に遅い。実行時間がデータ量の増加以上に伸びる。
Spark UI での確認箇所:ステージのメトリクスにある Spill (Memory) / Spill (Disk) の値を見ます。この値が 0 でなければ、処理中のデータがエグゼキュータのメモリに収まらず、ディスクへの書き出しと読み戻しという高コストなI/Oが発生しています。
原因:1タスクが処理するパーティションが大きすぎる、シャッフルパーティション数が少なすぎる、エグゼキュータあたりのメモリが処理内容に対して不足している、など。
対策:

4. 診断フロー:症状 → Spark UI → 原因 → 対策

3つのボトルネックを1つの診断フローに統合します。分岐の決め手は「タスク間のばらつき」と「spill の有無」です。

症状:ステージ/ジョブが遅い6-1 の実行履歴で該当タスクまで絞り込み済み
Spark UI:最も遅い Stage の Summary Metrics を開く
task duration・shuffle read の Min/Median/Max を確認
Max だけ突出(例:Median 400MB / Max 5GB)原因:データスキュー
全タスクで shuffle 量が一様に巨大原因:シャッフル過多
Spill (Disk) が 0 より大きい原因:メモリ不足によるディスクスピル
それぞれの対策
AQE の skew join 処理を確認補助:ブロードキャスト結合・salting
ブロードキャスト結合・早期フィルタshuffle.partitions 調整(3-5)
パーティション数を増やすメモリ最適化インスタンスへ変更

図:Spark UI メトリクスによるボトルネック診断フロー

✅ この節のまとめ

練習問題

問1. あるジョブが遅いため Spark UI を確認すると、特定ステージのタスクあたり shuffle read が Min:400MB、Median:400MB、Max:5GB だった。まず確認・実施すべきこととして最も適切なのはどれか。

  1. クラスタのワーカーノード数を2倍にする
  2. AQE(Adaptive Query Execution)が有効で、スキュー結合の自動処理が働く状態かを確認する
  3. 各エグゼキュータのメモリを増やしたインスタンスタイプに変更する
  4. spark.sql.shuffle.partitions を既定値の2倍にする
解答と解説を見る

正解:B

Median が正常で Max だけ突出しているのはデータスキューの典型パターンであり、第一手は AQE のスキュー結合処理(肥大パーティションの自動分割)が有効かの確認です。Aのノード追加は並列度を上げるだけで、偏ったキーのデータは依然1タスクに集中するため解決しません。Cのメモリ増強は巨大タスクの症状を和らげるだけで根本原因の偏りは残ります。Dのパーティション数増加も、同一キーのデータは同じパーティションに集まるため効果が期待できません。

問2. Spark UI であるステージを確認したところ、ほぼすべてのタスクで「Spill (Disk)」に無視できない値が記録されていた。この現象の説明と対策として最も適切なのはどれか。

  1. データが破損しているサイン。テーブルを再作成する必要がある
  2. 1タスクが処理するデータがメモリに収まらずディスクに退避している。パーティション数を増やす、またはメモリの大きい構成に変更する
  3. 正常な動作であり、対応は不要である
  4. ネットワーク帯域の不足。同一リージョン内にクラスタを移動する
解答と解説を見る

正解:B

spill はメモリに収まらないデータのディスク退避で、書き出しと読み戻しの分だけ処理が遅くなります。全タスクで一様に発生している場合は、1タスクあたりのデータ量過大かメモリ不足が原因なので、パーティション数の増加(1タスクの負荷を軽くする)やメモリ最適化インスタンスへの変更が対策です。Aのデータ破損とは無関係です。Cはタスクが失敗しない点では正しく見えますが、性能劣化要因であり放置は不適切です。Dのネットワークはシャッフルの転送に関わりますが、spill はノード内のメモリとディスクの問題です。

問3. 大きなファクトテーブルと小さなマスタテーブル(数MB)の結合を含むジョブで、Spark UI を見るとこの結合のステージで大量の shuffle write が発生していた。シャッフル量を最も直接的に削減できる対策はどれか。

  1. 小さいマスタテーブルをブロードキャスト結合にして、シャッフルを回避する
  2. ファクトテーブルを collect() でドライバーに集めてから結合する
  3. 結合後に不要な列を drop する
  4. クラスタのオートスケーリングを有効にする
解答と解説を見る

正解:A

片側が十分小さい結合では、小さいテーブルを全エグゼキュータへ配布するブロードキャスト結合により、大きい側のシャッフルを丸ごと省けます。Bの collect はドライバーのメモリを圧迫し OOM の原因になる悪手です(6-4 参照)。Cの列削減は有効な一般則ですが「結合後」では手遅れで、シャッフル前に絞り込む必要があります。Dはリソースの増減であり、シャッフル量そのものは減りません。

問4. Spark UI の階層構造に関する説明として正しいものはどれか。

  1. Stage は wide 変換によるシャッフル境界で区切られ、Stage 内の各 Task は1つのパーティションを処理する
  2. Task はアクションごとに作られ、Job は各パーティションを処理する最小単位である
  3. 1つの Job は常に1つの Stage だけで構成される
  4. Stage 間ではシャッフルは発生せず、Task 間でのみ発生する
解答と解説を見る

正解:A

Job(アクション単位)→ Stage(シャッフル境界で分割)→ Task(パーティション単位、並列実行)という階層が正しい理解です。Bは Job と Task の説明が逆です。Cは誤りで、join や groupBy などの wide 変換があるたびに Stage は複数に分かれます。Dも逆で、シャッフルはまさに Stage と Stage の境界で発生するデータ再配置です。