本文へ移動

ホーム / Data / 第4部 データ基盤

第4部 データ基盤・第10章

分散処理(Spark)

★★☆ 重要7トピック・面接 3問

1台に収まらないデータを多くのマシンで処理する仕組みと、遅くなる理由を押さえる。

10.1Spark の基本

用語★★☆ 重要
ひとことで

データを分けて多くのマシンで並列に処理する。データの偏り(スキュー)とシャッフルが遅さの原因。

Spark は、大きなデータを小さな塊(パーティション)に分け、多くのマシン(エグゼキューター)で並列に処理するエンジンです。DataFrame や SQL で書くと、Spark が実行計画を作って分散させます。DWH の SQL では難しい大量のファイル処理や、Python での複雑な変換、機械学習の前処理によく使います。

言葉意味
ドライバー実行計画を作り、仕事を配る役
エグゼキューター実際に計算するマシン(のプロセス)
パーティションデータの小さな塊。1つのタスクが1つを処理する
DataFrame・SQLSpark に「何をしたいか」を伝える書き方。Spark が最適な手順を考える

面接ではこう答える

Spark は、大きなデータをパーティションという小さな塊に分け、多くのエグゼキューターで並列に処理するエンジンです。DataFrame や SQL で書くと、ドライバーが実行計画を作って仕事を配ります。DWH の SQL では難しい大量のファイルの処理や Python での複雑な変換、ML の前処理によく使います。遅くなる主な原因は、データを送り合うシャッフルと、データの偏りのスキューです。

ひとことで

変換は書いただけでは動かず、アクションのときにまとめて計画し、ジョブ・ステージ・タスクに分けて実行する。

Spark では、filter や join などの変換を書いただけでは何も計算されません。write や count などのアクションを呼んだときに、それまでの変換をまとめて見て、むだのない手順を考えてから実行します(遅延評価)。

アクションを呼んだときに、まとめて計画して実行する変換を書くfilter・joinアクションwrite・countジョブ実行計画を作るステージシャッフルで区切るタスク塊ごとに並列
変換は書いただけでは動かず、アクションで初めて実行される(遅延評価)
単位意味Spark の画面で見ること
ジョブアクション1つ分の仕事どのアクションが時間を使っているか
ステージシャッフルで区切られた段階シャッフルの読み書きの量
タスク1つのパーティションを処理する単位タスクごとの時間の差(偏り)

同じ DataFrame を何度もアクションに使うと、そのたびに最初から計算し直します。何度も使う途中の結果は cache するか、いったん書き出します。

面接ではこう答える

Spark は遅延評価で、filter や join のような変換は書いただけでは計算されず、write や count のようなアクションを呼んだときにまとめて実行計画を作ります。仕事はアクションごとのジョブ、シャッフルで区切られたステージ、パーティションごとのタスクに分かれます。遅いジョブを調べるときは、Spark の画面でどのステージが長く、タスクの時間に差がないかを見ます。

10.3シャッフル

用語★★☆ 重要
ひとことで

GROUP BY や JOIN のために、同じキーのデータを同じマシンに集めるよう、ネットワーク越しに送り合う処理。

GROUP BY の前に、同じキーを同じマシンに集めるマシン1・2・3キーが混ざっているシャッフルキーごとに送り合うマシン1キー A だけ集計キーごとに合計
ネットワークとディスクを大量に使うので、Spark が遅いときの主な原因になる

シャッフルは、GROUP BY・JOIN・DISTINCT・並べ替えのときに、同じキーのデータを同じマシンに集めるため、ネットワーク越しにデータを並べ替えて送り合う処理です。ディスクとネットワークを大量に使うので、Spark が遅いときの主な原因です。不要な列や行はシャッフルの前に落とし、小さいテーブルとの JOIN はブロードキャスト JOIN(小さいほうを全マシンに配る)にしてシャッフルをなくします。

シャッフルが起きる処理減らす工夫
GROUP BY・集計先に不要な列と行を落とす
JOIN小さい表ならブロードキャスト JOIN にする
DISTINCT・並べ替え本当に要るかを見直す
repartition必要なときだけ。出力のファイル数の調整なら coalesce も検討

シャッフルのあとのパーティション数は spark.sql.shuffle.partitions(既定は 200)で決まります。AQE が有効なら、実行中に小さなパーティションをまとめてくれます。

面接ではこう答える

シャッフルは、GROUP BY や JOIN、DISTINCT、並べ替えのときに、同じキーのデータを同じマシンに集めるため、データをネットワーク越しに送り合う処理です。ディスクとネットワークを大量に使うので、Spark が遅いときの主な原因です。シャッフルの前に不要な列と行を落とし、小さい表との JOIN はブロードキャストにしてシャッフル自体をなくします。

ひとことで

特定のキーにデータが集中し、そのキーを担当する1つのタスクだけがいつまでも終わらないこと。

スキュー(データの偏り)は、特定のキーにデータが集中することです。たとえば user_id が NULL の行や、特定の大口の取引先にデータの大半が集まると、そのキーを担当する1つのタスクだけがいつまでも終わらず、全体がそれを待ちます。Spark の画面でタスクの所要時間に極端な差があれば、スキューを疑います。

1つのタスクだけ終わらない=スキュー入力キーごとに集める入力 1入力 2入力 3キー Aキー B(大口)キー Cネットワーク越しに並べ替えて送り合う=シャッフルタスクの所要時間t1t2t3t4t5t6t7t8t4 が終わるまで、全体が待つ
シャッフル(左)と、スキューのあるときのタスクの所要時間(右)
対策やり方
AQE(適応的クエリ実行)実行中に偏ったパーティションを自動で分割する。Spark 3.2.0 以降は既定で有効(spark.sql.adaptive.enabled)
ブロードキャスト JOIN片方が小さければ、シャッフル自体をなくす
NULL や偏ったキーを分けるNULL キーの行は JOIN の前に別に処理する
ソルティング偏ったキーに乱数の番号を足して分散させ、集計したあとでまとめ直す

面接ではこう答える

スキューは、NULL のキーや大口の取引先など、特定のキーにデータが集中することです。シャッフルで同じキーは同じタスクに集まるので、そのタスクだけが長くかかり、全体がそれを待ちます。Spark の画面でタスクの時間に極端な差があれば疑い、AQE の偏りの自動分割、ブロードキャスト JOIN、NULL キーを分けて処理する、偏ったキーに乱数を足して散らすソルティング、で対策します。

分散処理の JOIN は、どちらの表がどれくらい大きいかで方式が変わります。Spark は表の大きさの見積もりから自動で選びますが、見積もりが外れると遅い方式になることがあります。

2つの JOIN の方式ブロードキャスト JOIN小さい表を全マシンに配る大きい表はシャッフルしない小さい表が大きいとメモリ不足ソートマージ JOIN両方をキーでシャッフルして並べる大きい表どうしでも動くシャッフルが重い・偏りに弱い
片方が小さければブロードキャスト、両方大きければソートマージ
状況すること
片方が小さいのにソートマージになっているbroadcast() のヒントで指定する。自動で選ぶ大きさの上限は spark.sql.autoBroadcastJoinThreshold
両方大きく、毎回同じキーで JOIN する同じキーで並べた形(バケット)で保存しておく
JOIN のキーに偏りがあるAQE の偏りの分割を使う、偏ったキーを分けて処理する

1つのタスクが扱うデータがメモリに入らないと、Spark は途中の結果をディスクに書き出して続けます(スピル)。動きはしますが遅くなり、それでも足りないとエグゼキューターがメモリ不足(OOM)で落ちます。

1つのタスクが扱うデータの大きさと起きること小さすぎ(数 MB)タスクが多すぎて管理のむだほどよい速い大きすぎメモリに入らずスピル・OOM
パーティションの大きさと数を調整して、タスクごとの量をほどよくする
症状よくある原因対策
スピルが大きい1つのパーティションが大きすぎるシャッフルのパーティション数を増やす・AQE
一部のタスクだけ OOMスキュー偏りの対策(スキューの項)
ドライバーが OOMcollect() で全件をドライバーに集めた集めずに書き出す。見るだけなら少しだけ取る
全体に遅いエグゼキューターの数・メモリが足りない設定を見直す。まず不要なデータを落とす

ファイルを読むときのパーティションの大きさは spark.sql.files.maxPartitionBytes(既定は 128MB)で決まります。設定を変える前に、読むデータを減らせないかを先に考えます。

書き出しのときは、出力のファイル数にも注意します。パーティションが多すぎると小さなファイルが大量にでき、次に読む処理が遅くなります。

repartition と coalescerepartition(n)シャッフルして n 個に分け直す大きさがそろう重いcoalesce(n)シャッフルせずにまとめるだけ軽い減らすことしかできない・偏りが残る
出力のファイルを減らしたいだけなら、まず coalesce を検討する
  • partitionBy("dt") で日付ごとのフォルダに書くと、各タスクが各日付にファイルを作るので、タスク数×日付の数だけファイルができることがある。書く前に日付で並べ直すなどして減らす。
  • テーブル形式(Iceberg・Delta Lake)なら、書いたあとにまとめ直す機能も使える。
  • 1ファイルの大きさは、数百 MB 前後を目安にする(スモールファイル問題)。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • 遅延評価・ステージ・タスクと、シャッフル
  • スキュー(偏り)と JOIN の方式
  • メモリ・スピル・出力のファイル数
  • Spark の基本:データを分けて多くのマシンで並列に処理する。データの偏り(スキュー)とシャッフルが遅さの原因。
  • 遅延評価とジョブ・ステージ・タスク:変換は書いただけでは動かず、アクションのときにまとめて計画し、ジョブ・ステージ・タスクに分けて実行する。
  • シャッフル:GROUP BY や JOIN のために、同じキーのデータを同じマシンに集めるよう、ネットワーク越しに送り合う処理。
  • スキュー(データの偏り):特定のキーにデータが集中し、そのキーを担当する1つのタスクだけがいつまでも終わらないこと。

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1Spark のジョブで、1つのタスクだけがいつまでも終わりません。どうしますか?

模範回答

データの偏り(スキュー)を疑います。Spark の画面でタスクごとの時間や読んだデータ量を見て、1つだけ極端に大きければ、特定のキーにデータが集まっています。よくあるのは、キーが NULL の行や、大口の取引先・ボットのユーザーです。

対策は、まず AQE(適応的クエリ実行)の偏りの対策が有効かを確かめます。片方のテーブルが小さければブロードキャスト JOIN にしてシャッフルをなくします。NULL のキーは JOIN の前に別に処理し、どうしても偏るキーには乱数の番号を足して分散させ(ソルティング)、集計のあとでまとめ直します。

深掘りされたら

  • シャッフルを減らす書き方には、ほかに何がありますか?

見られているポイントスキューの見つけ方と、対策の引き出し

Q2Spark のシャッフルとは何ですか?なぜ遅くなるのですか?

模範回答

シャッフルは、GROUP BY や JOIN のために、同じキーのデータを同じマシンに集める処理です。各マシンが持っているデータをキーごとに分けて、ネットワーク越しに送り合います。

送る前にディスクに書き、受け取る側も読み込むので、ディスクとネットワークを大量に使い、ステージの区切りにもなります。減らすには、シャッフルの前に不要な列と行を落とす、小さい表との JOIN はブロードキャストにする、要らない DISTINCT や並べ替えをやめる、といった工夫をします。

深掘りされたら

  • シャッフルの後のパーティション数はどう決めますか?

見られているポイント仕組みと、減らす具体的な手段

Q3Spark のジョブがメモリ不足で落ちます。どう調べますか?

模範回答

まずどこで落ちているかを分けます。ドライバーなら、collect() で大量のデータを集めていないか、ブロードキャストしている表が大きすぎないかを見ます。エグゼキューターなら、Spark の画面で落ちたステージのタスクを見て、一部のタスクだけが大きければスキュー、全体に大きければパーティションが大きすぎる、と考えます。

スキューなら偏りの対策、全体ならシャッフルのパーティション数を増やす、そもそも読むデータを減らす、を先に試し、それでも足りなければメモリの設定を見直します。いきなりメモリを増やすだけだと、データが増えたときにまた落ちます。

深掘りされたら

  • スピルが多いときはどうしますか?

見られているポイント症状からドライバー・偏り・全体の大きさを切り分けられるか