a deeper understanding of spark internals (hadoop conference japan 2014)
Post on 08-Sep-2014
795 Views
Preview:
DESCRIPTION
TRANSCRIPT
A Deeper Understanding of Spark’s Internals
Patrick Wendell 07/08/2014
This Talk
• Goal: Understanding how Spark runs, focus on performance
• Major core components:
– Execution Model
– The Shuffle
– Caching
ゴール:Sparkがどのように動作するかをパフォーマンスに注目して理解
主要なコンポーネント
- 実行モデル
- シャッフル
- キャッシュ
This Talk
• Goal: Understanding how Spark runs, focus on performance
• Major core components:
– Execution Model
– The Shuffle
– Caching
ゴール:Sparkがどのように動作するかをパフォーマンスに注目して理解
主要なコンポーネント
- 実行モデル
- シャッフル
- キャッシュ
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
なぜ内部を理解するのか?
ゴール:名前の「最初の文字」の出現頻度を求める。
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, Andy) (P, Pat) (A, Ahir)
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, Andy) (P, Pat) (A, Ahir)
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, Andy) (P, Pat) (A, Ahir)
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, Set(Ahir, Andy)) (P, Set(Pat))
(A, Andy) (P, Pat) (A, Ahir)
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, 2) (P, 1)
(A, Andy) (P, Pat) (A, Ahir)
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Why understand internals?
Goal: Find number of distinct names per “first letter”
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues(names => names.toSet.size)
.collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, 2) (P, 1)
(A, Andy) (P, Pat) (A, Ahir)
res0 = [(A, 2), (P, 1)]
ゴール:名前の「最初の文字」の出現頻度を求める。
なぜ内部を理解するのか?
Spark Execution Model
1. Create DAG of RDDs to represent computation
2. Create logical execution plan for DAG
3. Schedule and execute individual tasks
Spark の実行モデル
1. 計算を表現するRDDのDAGを作成する。
2. DAGに対する論理的な実行計画を作成する。
3. スケジューリングを行い、各タスクを実行する。
Step 1: Create RDDs
sc.textFile(“hdfs:/names”)
map(name => (name.charAt(0), name))
groupByKey()
mapValues(names => names.toSet.size)
collect()
ステップ1: RDDを作成する
Step 1: Create RDDs
HadoopRDD
map()
groupBy()
mapValues()
collect()
ステップ1: RDDを作成する
Step 2: Create execution plan
• Pipeline as much as possible
• Split into “stages” based on need to reorganize data
Stage 1 HadoopRDD
map()
groupBy()
mapValues()
collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, 2)
(A, Andy) (P, Pat) (A, Ahir)
res0 = [(A, 2), ...]
ステップ2: 実行計画を作成する
できる限り多くのパイプラインを作る
データ再構築の必要に応じて、「ステージ」に分割する
Step 2: Create execution plan
• Pipeline as much as possible
• Split into “stages” based on need to reorganize data
Stage 1
Stage 2
HadoopRDD
map()
groupBy()
mapValues()
collect()
Andy Pat Ahir
(A, [Ahir, Andy]) (P, [Pat])
(A, 2) (P, 1)
(A, Andy) (P, Pat) (A, Ahir)
res0 = [(A, 2), (P, 1)]
ステップ2: 実行計画を作成する
できる限り多くのパイプラインを作る
データ再構築の必要に応じて、「ステージ」に分割する
• Split each stage into tasks
• A task is data + computation
• Execute all tasks within a stage before moving on
Step 3: Schedule tasks ステップ3: タスクをスケジュールする
各ステージをタスクに分割する
タスクは、データ+計算 である
あるステージのタスクを全て実行したら、次のステージに進む
Step 3: Schedule tasks Computation Data
hdfs:/names/0.gz
hdfs:/names/1.gz
hdfs:/names/2.gz
Task 1
hdfs:/names/3.gz
…
Stage 1 HadoopRDD
map()
hdfs:/names/0.gz
Task 0
HadoopRDD
map()
hdfs:/names/1.gz
Task 1
HadoopRDD
map()
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
/names/0.gz
HadoopRDD
map() Time
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map() Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map() Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/2.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
/names/2.gz
HadoopRDD
map()
Time
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
/names/2.gz
HadoopRDD
map()
Time
/names/3.gz
HadoopRDD
map()
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
/names/2.gz
HadoopRDD
map()
Time
/names/3.gz
HadoopRDD
map()
Step 3: Schedule tasks
/names/0.gz
/names/3.gz
HDFS
/names/1.gz
/names/2.gz
HDFS
/names/2.gz
/names/3.gz
HDFS
/names/0.gz
HadoopRDD
map()
/names/1.gz
HadoopRDD
map()
/names/2.gz
HadoopRDD
map()
Time
/names/3.gz
HadoopRDD
map()
The Shuffle
Stage 1
Stage 2
HadoopRDD
map()
groupBy()
mapValues()
collect()
The Shuffle
Stage 1
Stage 2
• Redistributes data among partitions
• Hash keys into buckets
• Optimizations:
– Avoided when possible, if data is already properly partitioned
– Partial aggregation reduces data movement
ハッシュキーをバケットに分解する
データをパーティションに沿って再分配する
最適化:
- データが既に適切にパーティション化されていれば、可能な限りシャッフルを回避する。
- 部分集約で、データの移動を減らす。
The Shuffle
Disk
Stage 2
Stage 1
• Pull-based, not push-based
• Write intermediate files to disk
プッシュ型ではなくプル型で
中間ファイルはディスクに書く。
Execution of a groupBy()
• Build hash map within each partition
• Note: Can spill across keys, but a single key-value pair must fit in memory
A => [Arsalan, Aaron, Andrew, Andrew, Andy, Ahir, Ali, …], E => [Erin, Earl, Ed, …] …
groupBY() の実行
各パーティションごとにハッシュマップを構築する。
注意:
複数のキーに振り分けることはできる
しかし、1つのキーと値のペアは、メモリに収まる必要がある。
Done!
Stage 1
Stage 2
HadoopRDD
map()
groupBy()
mapValues()
collect()
What went wrong?
• Too few partitions to get good concurrency
• Large per-key groupBy()
• Shipped all data across the cluster
どこで間違えてしまうのか?
・ 並列度をよくしたいのに、パーティション数が少なすぎる
・ groupBy()で、1つのキーが大きすぎる
・ 全データをクラスタ内に転送してしまう
Common issue checklist
1. Ensure enough partitions for concurrency
2. Minimize memory consumption (esp. of sorting and large keys in groupBys)
3. Minimize amount of data shuffled
4. Know the standard library
1 & 2 are about tuning number of partitions!
よくある問題のチェックリスト
1. 十分なパーティション数を設定し、並列にする
2. メモリ消費量を最小化する
(特にソートとgroupByの巨大なキー)
3. シャッフルするデータ量を最小化する
4. 標準ライブラリを理解する
1と2は、パーティション数のチューニング!
Importance of Partition Tuning
• Main issue: too few partitions – Less concurrency – More susceptible to data skew – Increased memory pressure for groupBy,
reduceByKey, sortByKey, etc.
• Secondary issue: too many partitions • Need “reasonable number” of partitions
– Commonly between 100 and 10,000 partitions – Lower bound: At least ~2x number of cores in
cluster – Upper bound: Ensure tasks take at least 100ms
パーティションチューニングの重要性
一番の問題:パーティション数が少なすぎる 並列度が下がる
データの偏りに影響が出やすい
groupBy, reducebyKey, sortBykey等のメモリを圧迫する
二番目の問題:パーティション数が多すぎる
一般には 100 から 10,000 パーティションがよい
下限:少なくともクラスタのコア数の2倍
上限:各タスクの実行時間が少なくとも100msはある
Memory Problems
• Symptoms: – Inexplicably bad performance – Inexplicable executor/machine failures
(can indicate too many shuffle files too)
• Diagnosis: – Set spark.executor.extraJavaOptions to include
• -XX:+PrintGCDetails • -XX:+HeapDumpOnOutOfMemoryError
– Check dmesg for oom-killer logs
• Resolution: – Increase spark.executor.memory – Increase number of partitions – Re-evaluate program structure (!)
メモリ問題
症状:
不可解なほどパフォーマンスが悪い
不可解なほどエグゼキュータや
ノードで失敗が起こる(シャッフルファイルが多くなりすぎる) 診断:
spark.executor.extraJavaOptionsに以下の値を設定
dmesgでOOMキラーの
ログを確認する 解決策:
spark.executor.memoryを増やす
パーティション数を増やす
プログラム構造を見直す
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.toSet.size }
.collect()
1. Ensure enough partitions for concurrency
2. Minimize memory consumption (esp. of large groupBys and sorting)
3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.repartition(6) .map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.toSet.size }
.collect()
1. Ensure enough partitions for concurrency
2. Minimize memory consumption (esp. of large groupBys and sorting)
3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.repartition(6) .distinct() .map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.toSet.size }
.collect()
1. Ensure enough partitions for
concurrency 2. Minimize memory consumption (esp. of
large groupBys and sorting) 3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.repartition(6) .distinct() .map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.size } .collect()
1. Ensure enough partitions for
concurrency 2. Minimize memory consumption (esp. of
large groupBys and sorting) 3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.distinct(numPartitions = 6) .map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.size } .collect()
1. Ensure enough partitions for concurrency
2. Minimize memory consumption (esp. of large groupBys and sorting)
3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.distinct(numPartitions = 6) .map(name => (name.charAt(0), 1)) .reduceByKey(_ + _) .collect()
1. Ensure enough partitions for concurrency
2. Minimize memory consumption (esp. of large groupBys and sorting)
3. Minimize data shuffle 4. Know the standard library
間違いを修正する
1. 十分なパーティション数を設定し、並列度を良くする
2. メモリ消費量を最小化する
(特に大きな groupBy とソート)
3. データシャッフルを最小にする
4. 標準ライブラリを理解する
Fixing our mistakes
sc.textFile(“hdfs:/names”)
.distinct(numPartitions = 6) .map(name => (name.charAt(0), 1)) .reduceByKey(_ + _) .collect()
Original: sc.textFile(“hdfs:/names”)
.map(name => (name.charAt(0), name))
.groupByKey()
.mapValues { names => names.toSet.size }
.collect()
間違いを修正する
Demo: Using the Spark UI
デモ: Spark UI を使う
Tools for Understanding Low-level Performance
jps | grep Executor
jstack <pid>
jmap -histo:live <pid>
低レベルのパフォーマンスを把握するためのツール
Other Thoughts on Performance
Start small and without caching, then scale your workload
If you can write your jobs in terms of SparkSQL, we can optimize them for you
パフォーマンスに関する他の考え
キャッシュなしで小さく初めて、その後負荷を大きくしていく
SparkSQL でジョブが書けるのなら、ジョブの最適化が可能
Questions?
top related