🎓 レベル:標準 | 重要度:A(必須)
📎 前提:MapReduceの考え方 | 関連:シャッフルとパーティショニング・SQLの基礎(結合・集約・サブクエリ)
要点(BLUF)
- Sparkは、MapReduceの後継となったインメモリ分散処理エンジン。中間結果をディスクでなくメモリに保持し、反復処理が桁違いに速い。
- データはRDD/DataFrameという「多数のパーティションに分かれた分散コレクション」。
DataFrameはSQLのような高水準APIで、最適化(Catalyst)が効く。 - 核心は遅延評価(lazy evaluation)。
filterやselectなどの**変換(transformation)は即実行されず、countやcollectなどのアクション(action)**で初めて、最適化済みのDAGとしてまとめて実行される。
概念 ── 分散コレクションを操作する
Sparkでは、巨大なデータを「多数のパーティションに分割された1つのコレクション」として扱い、map/filter/groupBy/joinをあたかも手元のリスト操作のように書きます。実体は各パーティションが別マシンで並列処理されます。
flowchart LR
DF["DataFrame(論理的には1つの表)"] --> P1["partition 1(マシンA)"]
DF --> P2["partition 2(マシンB)"]
DF --> P3["partition 3(マシンC)"]
DataFrameは行と名前付き列を持つ表(→ SQLの基礎(結合・集約・サブクエリ) のテーブルと同じ感覚)で、実際 Spark SQL としてSQLでも書けます。
仕組み ── 遅延評価とDAG
変換は「何をするかの記録」を積むだけで、すぐには動きません。アクションが呼ばれた瞬間、Sparkは積まれた変換を**DAG(有向非巡回グラフ)**として最適化し、まとめて実行します。
# 変換(transformation):ここではまだ実行されない(記録するだけ)
df2 = (df.filter(df.amount > 1000) # 行を絞る
.select("region", "amount") # 列を絞る
.groupBy("region").sum()) # 集約
# アクション(action):ここで初めてDAGが最適化され実行される
df2.show()
flowchart LR
T1["filter"] --> T2["select"] --> T3["groupBy/sum"] --> A["show(アクション)"]
A -.トリガー.-> EX["DAGを最適化して一括実行"]
遅延にする利点は最適化の余地。「あとでgroupByするなら、先にfilter/selectして読む量を減らそう」といった述語/射影プッシュダウンをSparkが自動で行えます。ラボでこの効果(全2000行→必要1000行だけ評価)を数で確認しました。
実行結果(実機・純Pythonで効果を再現):
全行数: 2000
素朴: 全2000行スキャン後に絞る -> 1000行
プッシュダウン: 必要な1000行だけ評価(無駄スキャン削減)
設計の勘所 ── 変換とアクション・耐障害
| 種類 | 例 | 性質 |
|---|---|---|
| 変換(lazy) | filter, select, groupBy, join | 記録のみ・即実行しない |
| アクション | count, collect, show, write | DAGをトリガーし実行 |
- 系譜(lineage):Sparkは「このRDDはどの変換から作られたか」を覚えており、パーティションが失われても再計算で復元できる(耐障害)。これは MapReduceの考え方 の「失敗タスクだけ再実行」を一般化したもの。
- DataFrame > RDD:高水準のDataFrame/SQLはCatalystオプティマイザが効くため、素のRDDより速く書きやすい。新規はDataFrame推奨。
なぜそうするか ── メモリと最適化
なぜMapReduceでなくSparkなのか。(1) インメモリ:MapReduceは段ごとに中間結果をディスクに書くため、反復(機械学習・グラフ処理)で遅い。Sparkはメモリ保持で反復が速い。(2) 遅延評価+最適化:処理全体を見てから実行計画を立てられるので、読む量・移動量を自動で削れる。手続きを1つずつ即実行する素朴な方式では、この全体最適ができません。「全部の手順を見てから、一番無駄のない順で動く」——これが速さと書きやすさの源です。
⚠️ よくある落とし穴
collect()で全件をドライバに集める → メモリ溢れ(OOM)。巨大データはwriteで分散保存。- 変換が即実行されると思い込む → 遅延なので、エラーがアクション時にまとめて出て戸惑う。
- 不要な
groupBy/joinでshuffle多発 → 遅い(→ シャッフルとパーティショニング)。 - RDDで低水準に書きすぎる → 最適化が効かない。DataFrame/SQLを使う。
対応ラボ
data-engineering-study/labs/06_distributed.py(PYTHONIOENCODING=utf-8 で実行・プッシュダウン効果を確認済み。Spark APIは本文に言語明示で例示)。
関連
- 原点の処理モデルは MapReduceの考え方
- 性能を左右するshuffle/パーティションは シャッフルとパーティショニング
- DataFrameの表操作の土台は SQLの基礎(結合・集約・サブクエリ)