a deeper understanding of spark internals (hadoop conference japan 2014)

Post on 08-Sep-2014

795 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

■Hadoop Conference Japan 2014 講演資料 https://hcj2014.eventbrite.com/ 『A Deeper Understanding of Spark Internals』 Patrick Wendell (Databricks)

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