⚡ STEP 2: Apache Sparkアーキテクチャ
Sparkの内部構造を理解して、効率的なコードを書けるようになる
📋 このステップで学ぶこと
- Sparkのアーキテクチャ全体像
- Driver、Executor、Cluster Managerの役割
- マスターとワーカーの関係
- Spark Coreの理解
- RDD、DataFrame、Datasetの位置づけ
🎯 1. Sparkのアーキテクチャ全体像
1-1. なぜアーキテクチャを学ぶのか
Sparkのコードを書く前に、「Sparkがどのように動いているか」を理解することが大切です。アーキテクチャを理解していると、以下のようなメリットがあります。
✅ エラーが発生したときに、原因を特定しやすくなる
✅ パフォーマンスを改善するポイントがわかる
✅ メモリ不足などの問題を事前に防げる
✅ クラウド環境でのリソース設定が適切にできる
1-2. 身近な例で理解するSparkの構造
Sparkの構造を、「オーケストラの演奏」に例えて説明します。
オーケストラ全体を統率する存在。
・楽譜(コード)を読み解く
・各楽器(Executor)に演奏指示を出す
・全体のタイミングを調整する
・演奏が完成したら聴衆に届ける
実際に音を出す存在。
・指揮者の指示に従って演奏する
・自分の楽譜(データ)を処理する
・他の演奏者と並行して演奏
・複数人が同時に演奏して美しい音楽を作る
演奏環境を整える存在。
・会場や楽器を準備する
・演奏者を手配・配置する
・演奏者が足りなければ追加する
・演奏者が倒れたら代役を用意する
1-3. Sparkクラスターの全体図
Sparkは、「マスター・ワーカー型」のアーキテクチャを採用しています。1台のマスター(司令塔)が、複数のワーカー(作業員)を管理します。
※以下の図は横スクロールできます(スマートフォンの場合)
■ Driver Program(指揮者)
├ あなたのコードを実行
├ ジョブを小さなタスクに分割
├ Executorにタスクを割り当て
└ 結果を集めてまとめる
↓ 「このタスクをやって」
■ Cluster Manager(劇場マネージャー)
└ リソース(CPU・メモリ)を管理
↓ Executorを起動
■ Worker 1〜3(作業マシン)
┌─────────────┬─────────────┬─────────────┐
│ Worker 1 │ Worker 2 │ Worker 3 │
│ Executor │ Executor │ Executor │
│(演奏者) │(演奏者) │(演奏者) │
│ Task 1, 4 │ Task 2, 5 │ Task 3, 6 │
└─────────────┴─────────────┴─────────────┘
↓ 「処理が終わりました」
→ Driver に結果を返却
1-4. 処理の流れを順番に追う
Sparkアプリケーションが実行されるとき、内部では以下の順番で処理が進みます。
STEP 1: アプリケーション起動
あなた → Pythonでsparkのコードを実行
Driver → SparkSession(Sparkとの接続)を初期化
STEP 2: リソース確保
Driver → 「4コア、8GBメモリください」
Cluster Manager → 利用可能なWorkerを確認
Cluster Manager → 各WorkerでExecutorを起動
STEP 3: ジョブの分割と実行
Driver → ジョブを小さなTaskに分割
Driver → 各ExecutorにTaskを割り当て
Executor → Taskを並列で実行(ここで高速化!)
Executor → 処理した中間結果をメモリに保存
STEP 4: 結果の集約
Executor → 処理結果をDriverに送信
Driver → 全Executorの結果を統合
Driver → 最終結果をあなたに返す
STEP 5: リソース解放
Driver → 「処理完了しました」
Cluster Manager → Executorを停止
Driver → SparkSessionを終了
Sparkが速い理由は、STEP 3で複数のExecutorが並列で処理するからです。
10台のExecutorがあれば、理論上10倍速く処理できます。
🎭 2. Driver、Executor、Cluster Managerの役割
2-1. Driver(ドライバー)を詳しく理解する
Driverは、Sparkアプリケーションの「脳」です。あなたが書いたコードはすべてDriverで実行され、Driverが全体を制御します。
1. SparkSessionの管理
Sparkクラスターとの接続を確立し、維持する
2. ジョブの分割
大きな処理を小さなタスクに分割する
3. DAG(処理計画)の作成
処理の順番と依存関係を整理する
4. タスクの割り当て
どのExecutorにどのタスクをやらせるか決める
5. 結果の集約
各Executorから返ってきた結果をまとめる
Driverで実行されるコード例
以下は、Driverで実行される典型的なSparkコードです。
from pyspark.sql import SparkSession # ━━━ ここからDriverで実行 ━━━ # SparkSessionを作成(Sparkとの接続を確立) spark = SparkSession.builder \ .appName("MyApp") \ .getOrCreate() # データを読み込む(この時点では「計画」だけ) df = spark.read.csv("data.csv") # データを加工(まだ実行されない) result = df.filter(df['age'] > 30) \ .groupBy('city') \ .count() # show()を呼ぶと、ここで初めて処理が実行される result.show() # SparkSessionを終了 spark.stop()
1. Driverが止まると全体が止まる
Driverは「脳」なので、これが停止するとアプリケーション全体が停止します。
2. Driverに大量データを集めない
collect()で全データをDriverに集めると、メモリ不足になります。
例:1億行のデータをdf.collect()すると、Driverがダウンします。
3. ネットワーク通信が重要
DriverとExecutor間の通信が遅いと、全体のパフォーマンスが落ちます。
2-2. Executor(エグゼキューター)を詳しく理解する
Executorは、実際にデータを処理する「手足」です。Driverからの指示を受けて、並列でタスクを実行します。
1. タスクの実行
Driverから受け取ったタスクを処理する
2. データの保持
処理中の中間データをメモリに保存する
3. キャッシュの管理
頻繁に使うデータをメモリに保存して再利用
4. 結果の送信
処理が終わったらDriverに結果を返す
Executorの内部構造
1つのExecutorは、以下のような構造になっています。
■ Executor 1(Worker Node 1 上で動作)
CPU Cores: 4コア
└ 同時に4つのTaskを実行可能
Memory: 8GB
├ Execution Memory(約60%): 4.8GB
│ └ タスクの実行に使用(ソート、結合、集計など)
└ Storage Memory(約40%): 3.2GB
└ キャッシュデータの保存(cache()で保存したデータ)
現在実行中のTask:
├ Task 1: データ読み込み中
├ Task 2: フィルター処理中
├ Task 3: 集計処理中
└ Task 4: 待機中
1つのExecutorは、CPUコア数と同じ数のTaskを同時実行できます。
例:4コアのExecutorの場合
・4つのTaskを同時に実行可能
・5つ目のTaskは、1つが終わるまで待機
つまり:コア数が多いほど、並列処理能力が高い!
2-3. Cluster Manager(クラスターマネージャー)を詳しく理解する
Cluster Managerは、クラスター全体の「リソース管理者」です。CPUやメモリの割り当てを管理し、Executorの起動・停止を行います。
Sparkは複数のCluster Managerに対応しています。
1. Local → 開発・学習用(1台のPC)
2. Standalone → Spark専用の軽量版
3. YARN → Hadoop環境で最も一般的
4. Kubernetes → コンテナ環境で人気上昇中
5. Mesos → 大規模クラスター向け
| Cluster Manager | 特徴 | 使用場面 |
|---|---|---|
| Local | 1台のPCで動作 セットアップ不要 |
学習、開発、テスト |
| Standalone | Spark専用 シンプルで軽量 |
小規模クラスター |
| YARN | Hadoopと統合 複数アプリ共存 |
AWS EMR、GCP Dataproc |
| Kubernetes | コンテナベース 柔軟なスケーリング |
クラウドネイティブ環境 |
Part 1〜5: Localモード(自分のPC)で基礎を学習
Part 6: AWS EMR(YARN)とGCP Dataproc(YARN)で本番環境を体験
👥 3. マスターとワーカーの関係
3-1. マスター・ワーカーモデルとは
マスター・ワーカーモデルは、分散システムで最も一般的なアーキテクチャです。「司令塔が1つ、作業員が複数」という構造です。
■ Master Node(マスターノード)
├ Driver Program
└ Cluster Manager
↓ 指示を出す
■ Worker Nodes(ワーカーノード)
┌─────────────┬─────────────┬─────────────┐
│ Worker 1 │ Worker 2 │ Worker 3 │
│(ワーカー1)│(ワーカー2)│(ワーカー3)│
│ Executor │ Executor │ Executor │
│ Data 1/3 │ Data 2/3 │ Data 3/3 │
└─────────────┴─────────────┴─────────────┘
3-2. データの分散配置
ビッグデータは1台のマシンには収まりません。そこでSparkは、データをパーティション(区画)に分割して、各ワーカーに分散配置します。
パーティションとは、データを小分けにした「塊」のことです。
例:1億行のデータがある場合
→ 100個のパーティションに分割(各パーティションは100万行)
→ 各ワーカーが担当するパーティションを処理
→ 並列で処理するため高速!
元データ: 1億レコード(100GB)
━━━ パーティション分割 ━━━
Worker 1 が処理:
├ パーティション 1(1,000万レコード、10GB)
├ パーティション 4(1,000万レコード、10GB)
└ パーティション 7(1,000万レコード、10GB)
Worker 2 が処理:
├ パーティション 2(1,000万レコード、10GB)
├ パーティション 5(1,000万レコード、10GB)
└ パーティション 8(1,000万レコード、10GB)
Worker 3 が処理:
├ パーティション 3(1,000万レコード、10GB)
├ パーティション 6(1,000万レコード、10GB)
├ パーティション 9(1,000万レコード、10GB)
└ パーティション 10(1,000万レコード、10GB)
━━━ 結果 ━━━
1台で処理: 約60分
3台で処理: 約20分(3倍高速!)
3-3. 障害発生時の動作
分散システムでは、どこかのマシンが故障することは珍しくありません。Sparkは、障害が発生しても処理を継続できるように設計されています。
1. Executorが故障した場合
→ Driverが検知して、別のExecutorでタスクを再実行
→ 処理は継続される
2. Workerノードが故障した場合
→ そのノード上の全Executorを他のノードで再起動
→ データの系譜(Lineage)から再計算
3. Driverが故障した場合
→ アプリケーション全体が停止
→ 最初から再実行が必要
Sparkは、データがどのように作られたかの「履歴」を記録しています。
例えば「データAをフィルターして、グループ化して、集計した」という履歴があれば、
途中でデータが失われても、元のデータから再計算できます。
これにより、高い耐障害性(Fault Tolerance)を実現しています。
⚙️ 4. Spark Coreの理解
4-1. Spark Coreとは
Spark Coreは、Sparkの「基盤」となるコンポーネントです。すべてのSpark機能は、このSpark Coreの上に構築されています。
■ 高レベルAPI(ユーザーが使う部分)
├ Spark SQL / DataFrame
├ MLlib(機械学習)
└ Structured Streaming
↓ 内部で使用
■ Spark Core(基盤)
├ RDD(基本データ構造)
├ タスクスケジューリング
├ メモリ管理
└ 障害復旧
↓ 実行基盤
■ Cluster Manager(YARN / Kubernetes等)
4-2. Spark Coreの主要機能
Resilient Distributed Dataset(耐障害性分散データセット)の略。
・分散データの基本単位
・不変(Immutable)
・障害に強い
ジョブを効率的に実行するための仕組み。
・ジョブをステージに分割
・ステージをタスクに分割
・データの場所を考慮して割り当て
高速処理のためのメモリ活用。
・インメモリ処理で高速化
・キャッシュの管理
・メモリ不足時のディスク退避
データの系譜(Lineage)を記録。
・障害時は失われた部分のみ再計算
・チェックポイントで復旧を高速化
・高い耐障害性を実現
4-3. TransformationsとActions(最重要概念)
Sparkを使う上で最も重要な概念が、「Transformations(変換)」と「Actions(アクション)」の違いです。
データを「変換」する操作。すぐには実行されない(計画を立てるだけ)。
例:filter、map、groupBy、select、join など
特徴:
・新しいDataFrame/RDDを返す
・遅延評価(Lazy Evaluation)
・「こういう処理をする」という計画を立てるだけ
結果を取得したり保存したりする操作。ここで初めて実行される。
例:show、count、collect、save、first など
特徴:
・結果を返す(数値、リスト、画面表示など)
・Transformationsをまとめて実行
・実際にExecutorが動き始める
# ━━━ Transformations(変換)━━━ # この時点では「計画を立てているだけ」で、実際の処理は行われない df = spark.read.csv("data.csv") # 計画1: CSVを読む df2 = df.filter(df['age'] > 30) # 計画2: 30歳以上でフィルター df3 = df2.groupBy('city') # 計画3: 都市でグループ化 df4 = df3.count() # 計画4: カウント # ↑ここまで、実際には何も処理されていない! # Sparkは「こういう処理をする予定」という計画を立てているだけ # ━━━ Actions(アクション)━━━ # show()を呼んだ瞬間、上記の計画が一気に実行される df4.show() # ← ここで初めて処理が実行される!
理由1: 最適化ができる
Sparkは処理全体を見てから最適な実行計画を立てます。
例:フィルター処理を先に実行して、処理するデータ量を減らす
理由2: 無駄な処理を省ける
必要な結果を得るために本当に必要な処理だけを実行します。
理由3: メモリ効率が良い
中間結果を必要以上にメモリに保持しません。
📊 5. RDD、DataFrame、Datasetの位置づけ
5-1. Sparkのデータ抽象化の進化
Sparkには、データを扱うための3つの方法(API)があります。それぞれ登場した時期と特徴が異なります。
2012年:RDD 登場
└「低レベルで柔軟だけど、ちょっと難しい…」
↓
2013年:DataFrame 登場
└「Pandasみたいで使いやすい!SQLも使える!」
↓
2015年:Dataset 登場(Scala/Javaのみ)
└「型安全で、コンパイル時にエラーチェック!」
※Pythonでは使えない
↓
現在:DataFrameが主流
└ 99%のケースでDataFrameを使えばOK!
5-2. RDD(Resilient Distributed Dataset)
RDDは、Sparkの最初のデータ構造です。低レベルで柔軟性が高いですが、使いこなすには知識が必要です。
良い点:
・細かい制御ができる(低レベルAPI)
・任意のPythonオブジェクトを格納できる
・障害に強い(自動で再計算)
悪い点:
・使い方が難しい
・自動最適化が効かない
・SQLが使えない
RDDのコード例
from pyspark import SparkContext # SparkContextを作成 sc = SparkContext("local", "RDD Example") # リストからRDDを作成 rdd = sc.parallelize([1, 2, 3, 4, 5]) # Transformation: 各要素を2倍にする(まだ実行されない) rdd2 = rdd.map(lambda x: x * 2) # Action: 結果を取得(ここで実行される) result = rdd2.collect() print(result) sc.stop()
[2, 4, 6, 8, 10]
5-3. DataFrame(最も推奨)
DataFrameは、Pandasに似た高レベルなAPIです。使いやすく、自動最適化も効くため、最も推奨されます。
良い点:
・Pandasに似た直感的なAPI
・スキーマ(列名と型)が明確
・自動最適化が効く(Catalyst Optimizer)
・SQLクエリが使える
・Python、Scala、Java、Rすべてで使える
悪い点:
・RDDほど細かい制御はできない
・構造化データ向け(非構造化データは苦手)
DataFrameのコード例
from pyspark.sql import SparkSession # SparkSessionを作成 spark = SparkSession.builder.appName("DataFrame Example").getOrCreate() # データを用意(リストのリスト) data = [ (1, "Alice", 30), (2, "Bob", 25), (3, "Charlie", 35) ] # DataFrameを作成(列名も指定) df = spark.createDataFrame(data, ["id", "name", "age"]) # Transformation: 28歳以上でフィルター df_filtered = df.filter(df.age > 28) # Action: 結果を表示 df_filtered.show() spark.stop()
+---+-------+---+ | id| name|age| +---+-------+---+ | 1| Alice| 30| | 3|Charlie| 35| +---+-------+---+
5-4. Dataset(Scala/Javaのみ)
Datasetは、DataFrameに型安全性を追加したものです。ただし、Pythonでは使えません。
PythonではDatasetは使えません。DataFrameを使ってください。
実は、PySparkのDataFrameは内部的には「Dataset[Row]」(Rowという型のDataset)です。
つまり、PythonでDataFrameを使うことは、Datasetの一種を使っているのと同じです。
5-5. RDD vs DataFrame 比較表
| 項目 | RDD | DataFrame |
|---|---|---|
| 抽象化レベル | 低レベル | 高レベル |
| 使いやすさ | ⭐⭐(難しい) | ⭐⭐⭐⭐⭐(簡単) |
| パフォーマンス | 最適化なし | 自動最適化 |
| スキーマ | なし | あり(列名と型) |
| SQL対応 | ❌ 不可 | ✅ 可能 |
| 推奨度 | 特殊な用途のみ | ★ 推奨 ★ |
99%のケースでDataFrameを使ってください。
RDDは、DataFrameでは実現できない特殊な処理が必要な場合のみ使います。
例えば、非構造化データや、独自のオブジェクトを扱う場合などです。
このコースでは:
・Part 2: RDDの概念を理解(4時間)
・Part 3以降: DataFrameに集中して学習
📝 STEP 2 のまとめ
1. Sparkの3大コンポーネント
・Driver = 指揮者(コードを実行、タスクを割り当て)
・Executor = 演奏者(実際にデータを処理)
・Cluster Manager = 劇場マネージャー(リソースを管理)
2. マスター・ワーカーモデル
・1台のマスターが複数のワーカーを管理
・データはパーティションに分割して分散配置
・障害時はLineage(系譜)から再計算
3. Spark Core
・すべてのSpark機能の基盤
・RDD、スケジューリング、メモリ管理、障害復旧を担当
4. TransformationsとActions
・Transformations = 計画を立てる(すぐには実行されない)
・Actions = 実行する(ここで初めて処理が走る)
5. データ構造の選択
・RDD = 低レベル、特殊用途向け
・DataFrame = 高レベル、99%これを使う
・Dataset = Scala/Javaのみ(Pythonでは使えない)
「TransformationsとActions」の違いを理解することが、Sparkを使いこなす第一歩です。
Transformationsはすぐには実行されず、Actionを呼んだときに初めて実行されます。
この「遅延評価(Lazy Evaluation)」がSparkの高速化の秘密です。
次のSTEP 3では、「Spark環境構築」を行います。
いよいよ実際に手を動かします!ローカルPCにSparkをインストールして、初めてのSparkコードを実行しましょう!
学習メモ
ビッグデータ処理(Apache Spark) - Step 2