Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

A Deeper Understanding of Spark Internals (Hadoop Conference Japan 2014)

4,901 views

Published on

■Hadoop Conference Japan 2014 講演資料
https://hcj2014.eventbrite.com/

『A Deeper Understanding of Spark Internals』
Patrick Wendell (Databricks)

  • Be the first to comment

A Deeper Understanding of Spark Internals (Hadoop Conference Japan 2014)

  1. 1. A Deeper Understanding of Spark’s Internals Patrick Wendell 07/08/2014
  2. 2. This Talk • Goal: Understanding how Spark runs, focus on performance • Major core components: – Execution Model – The Shuffle – Caching ゴール:Sparkがどのように動作するかをパフォーマンスに注目して理解 主要なコンポーネント - 実行モデル - シャッフル - キャッシュ
  3. 3. This Talk • Goal: Understanding how Spark runs, focus on performance • Major core components: – Execution Model – The Shuffle – Caching ゴール:Sparkがどのように動作するかをパフォーマンスに注目して理解 主要なコンポーネント - 実行モデル - シャッフル - キャッシュ
  4. 4. 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() なぜ内部を理解するのか? ゴール:名前の「最初の文字」の出現頻度を求める。
  5. 5. 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() AndyPatAhir ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  6. 6. 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() AndyPatAhir (A, Andy)(P, Pat)(A, Ahir) ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  7. 7. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, Andy)(P, Pat)(A, Ahir) ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  8. 8. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, Andy)(P, Pat)(A, Ahir) ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  9. 9. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, Set(Ahir, Andy)) (P, Set(Pat)) (A, Andy)(P, Pat)(A, Ahir) ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  10. 10. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, 2) (P, 1) (A, Andy)(P, Pat)(A, Ahir) ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  11. 11. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, 2) (P, 1) (A, Andy)(P, Pat)(A, Ahir) res0 = [(A, 2), (P, 1)] ゴール:名前の「最初の文字」の出現頻度を求める。 なぜ内部を理解するのか?
  12. 12. 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. スケジューリングを行い、各タスクを実行する。
  13. 13. Step 1: Create RDDs sc.textFile(“hdfs:/names”) map(name => (name.charAt(0), name)) groupByKey() mapValues(names => names.toSet.size) collect() ステップ1: RDDを作成する
  14. 14. Step 1: Create RDDs HadoopRDD map() groupBy() mapValues() collect() ステップ1: RDDを作成する
  15. 15. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, 2) (A, Andy)(P, Pat)(A, Ahir) res0 = [(A, 2), ...] ステップ2: 実行計画を作成する できる限り多くのパイプライ ンを作る データ再構築の必要に応じて、「ステージ」に 分割する
  16. 16. 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() AndyPatAhir (A, [Ahir, Andy]) (P, [Pat]) (A, 2) (P, 1) (A, Andy)(P, Pat)(A, Ahir) res0 = [(A, 2), (P, 1)] ステップ2: 実行計画を作成する できる限り多くのパイプライ ンを作る データ再構築の必要に応じて、「ステージ」に 分割する
  17. 17. • Split each stage into tasks • A task is data + computation • Execute all tasks within a stage before moving on Step 3: Schedule tasks ステップ3: タスクをスケジュールする 各ステージをタスクに分割 する タスクは、データ+計算 で ある あるステージのタスクを全て実行したら、次の ステージに進む
  18. 18. 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()
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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()
  27. 27. 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()
  28. 28. 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()
  29. 29. The Shuffle Stage 1 Stage 2 HadoopRDD map() groupBy() mapValues() collect()
  30. 30. 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 ハッシュキーをバケットに分解する データをパーティションに沿って再分配する 最適化: - データが既に適切にパーティション化されていれば、可能な限りシャッフ ルを回避する。 - 部分集約で、データの移動を減らす。
  31. 31. The Shuffle Disk Stage 2 Stage 1 • Pull-based, not push-based • Write intermediate files to disk プッシュ型ではなくプル型で 中間ファイルはディスク に書く。
  32. 32. 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つのキーと値のペアは、メモリに収まる必要がある。
  33. 33. Done! Stage 1 Stage 2 HadoopRDD map() groupBy() mapValues() collect()
  34. 34. What went wrong? • Too few partitions to get good concurrency • Large per-key groupBy() • Shipped all data across the cluster どこで間違えてしまうのか? ・ 並列度をよくしたいのに、パーティション数が少なすぎる ・ groupBy()で、1つのキーが大きすぎる ・ 全データをクラスタ内に転送してしまう
  35. 35. 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は、パーティション数のチューニング!
  36. 36. 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はある
  37. 37. 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を増やす パーティション数を増やす プログラム構造を見直す
  38. 38. 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. 標準ライブラリを理解する
  39. 39. 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. 標準ライブラリを理解する
  40. 40. 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. 標準ライブラリを理解する
  41. 41. 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. 標準ライブラリを理解する
  42. 42. 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. 標準ライブラリを理解する
  43. 43. 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. 標準ライブラリを理解する
  44. 44. 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() 間違いを修正する
  45. 45. Demo: Using the Spark UI デモ: Spark UI を使う
  46. 46. Tools for Understanding Low-level Performance jps | grep Executor jstack <pid> jmap -histo:live <pid> 低レベルのパフォーマンスを把握するためのツール
  47. 47. 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 でジョブが書けるのなら、ジョブの最適化が可能
  48. 48. Questions?

×