Spark SQLモジュールは、主にSQL解析に関連するいくつかのコンテンツを処理するためのものであり、より一般的には、SQLステートメントをDataframeまたはRDDタスクに解析する方法です。Spark 2.4.3を例にとると、Spark SQLの大きなモジュールは、以下に示すように3つのサブモジュールに分割されます。
その中でも、CatalystはSpark内でSQLを解析するための専用フレームワークと言え、Hiveでの類似のフレームワークはCalcite(SQLをMapReduceタスクに解析)です。Catalystは、SQL解析タスクをいくつかの段階に分割します。これは、対応する論文でより明確に説明されています。このシリーズの多くの内容は、この論文も参照します。元の論文を読みたい人は、Spark SQL:Relational Data Processing in Sparkにアクセスできます。
コアモジュールは実際にはSpark SQLの主要な分析プロセスです。もちろん、Catalystの一部のコンテンツがこのプロセスで呼び出されます。このモジュールでより一般的に使用されるクラスには、SparkSession、DataSetなどがあります。
ハイブモジュールについては、ハイブに関連している必要があることは言うまでもありません。このモジュールは基本的にこのシリーズには含まれないため、あまり紹介しません。
ペーパーが公開されたとき、それはまだSpark1.xステージにあったことは言及する価値があります。そのとき、SQLはscalaで書かれた解析ツールを使用して字句ツリーに解析されました。2.xステージでは、antlr4がこの部分を行うために使用されましたこれが最大の変更になるはずです)。なぜ変更する必要があるのかは、読みやすさと使いやすさによるものだと思いますが、もちろんこれは個人的な推測です。
さらに、このシリーズでは、spark 2.4.3に基づくSQLステートメントの処理フローを簡単に紹介します(sqlモジュールは、spark2.1以降、あまり変更されていません)。この記事では、最初にSpark SQL全体の背景と問題解決を紹介し、Dataframe APIとCatalystのプロセスとは何かを紹介し、次にCatalystのプロセスを段階的に詳しく説明します。
Spark SQLの背景と問題
当初、大規模データ処理のテクノロジーはMapReduceでしたが、このフレームワークの実装効率は遅すぎ、一部のリレーショナル処理(結合など)には多くのコードが必要でした。その後、hiveなどのフレームワークにより、ユーザーはSQLステートメントを入力し、自動的に最適化して実行することができます。
ただし、大規模なシステムでは、主に2つの問題があります。1つは、ETL操作が複数のデータソースとやり取りする必要があることです。もう1つは、ユーザーが機械学習やグラフ計算などの複雑な分析を実行する必要があることです。しかし、従来のリレーショナル処理システムで実現することはより困難です。
Spark SQLは、この問題を解決するために、DataFrame APIとCatalystの2つのサブモジュールを提供します。
RDDと比較して、Dataframe APIはより豊富なリレーショナルAPIを提供し、RDDで変換できます。また、後でSpark機械学習の焦点も、RDDベースのmllibからDataframeベースのSpark ML(データフレームの下部もRDDですが)。
もう1つはCatalystです。これを使用すると、データソース(jsonやケースクラスを介したカスタムタイプなど)を機械学習などのドメインに簡単に追加し、ルールとデータタイプを最適化できます。
これら2つのモジュールにより、Spark SQLは主に次の目標を達成します。
- 外部データソースの読み取りやリレーショナルデータ処理など、使いやすいAPIを提供する
- 確立されたDBMSテクノロジーを使用して、高いパフォーマンスを提供します。
- 半構造化データや外部データベース(MYSQLなど)を含む新しいデータソースを簡単にサポートします。
- グラフコンピューティングと機械学習の拡張
次に、DataframeとCatalystのプロセスを紹介します。もちろん、主な議論はCatalystです。
統合APIデータフレーム
最初に、紙で提供された写真を見てください。
この図は、まず第一に、SparkのDataframe APIの最下層もSparkのRDDに基づいて多くのことを説明できます。ただし、RDDとの違いは、Dataframeがスキーマを保持し(これは変換するのが非常に難しいため、データの構造として理解できる)、選択、フィルター、結合、Groupbyなどのさまざまなリレーショナル操作を実行できることです。運用の観点からは、pandas Dataframeに似ています(名前は同じです)。
同時に、RDDに基づいているため、分散コンピューティングの一貫性、信頼性の保証など、Dataframeが享受できる多くのRDD機能、およびキャッシュを介してデータをキャッシュしてコンピューティングパフォーマンスを向上させることができます。
同時に、図のページは、JDBC、コンソール操作(スパークシェル)、またはユーザープログラムを介して、Dataframeを外部データベースに接続できることを示しています。端的に言うと、DataframeはRDDで変換するか、外部データテーブルで生成できます。
ちなみに、最初にSpark SQLに公開されている多くの子供の靴は、DatasetとDataframeの2つについて混乱している可能性があります。1.xの時代には、実際には多少異なりますが、spark2.xでは、これら2つのAPI統一されました。したがって、基本的にはDatasetとDataframeは同等と見なすことができます。
最後に、コードと組み合わせて実用的な表示を作成します。RDDの生成を以下に示します。対応するDataframeはこのRDDに従って生成され、RDDとDataframeの違いを確認できます。
//生成RDD
scala> val data = sc.parallelize(Array((1,2),(3,4)))
data: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[0] at parallelize at <console>:24
scala> data.foreach(println)
(1,2)
(3,4)
scala> val df = data.toDF("fir","sec")
df: org.apache.spark.sql.DataFrame = [fir: int, sec: int]
scala> df.show()
+---+---+
|fir|sec|
+---+---+
| 1| 2|
| 3| 4|
+---+---+
//跟RDD相比,多了schema
scala> df.printSchema()
root
|-- fir: integer (nullable = false)
|-- sec: integer (nullable = false)
触媒流動分析
Catalystは論文ではオプティマイザーと呼ばれています。この部分は論文の中心的な内容ですが、プロセスは実際には非常に簡単に理解できます。論文の写真はまだ投稿されています。
メインプロセスは、次のステップに大別できます。
- SqlステートメントはAntlr4によって解析され、未解決の論理プランが生成されます(Antlr4を使用した子供の靴は、このプロセスに精通している必要があります)
- アナライザーとカタログはバインドされ(カタログストアメタデータ)、論理プランを生成します。
- オプティマイザは論理プランを最適化し、最適化された論理プランを生成します。
- SparkPlanは最適化されたLogicalPlanを物理プランに変換します。
- prepareForExecution()は、物理計画を実行済みの物理計画に変換します。
- execute()は、実行可能な物理計画を実行し、RDDを取得します。
上記のプロセスのほとんどはorg.apache.spark.sql.execution.QueryExecutionクラスにあります。この投稿は単純なコードですので、見てください。後の記事で内容を詳しく説明します。
class QueryExecution(val sparkSession: SparkSession, val logical: LogicalPlan) {
......其他代码
//analyzer阶段
lazy val analyzed: LogicalPlan = {
SparkSession.setActiveSession(sparkSession)
sparkSession.sessionState.analyzer.executeAndCheck(logical)
}
//optimizer阶段
lazy val optimizedPlan: LogicalPlan = sparkSession.sessionState.optimizer.execute(withCachedData)
//SparkPlan阶段
lazy val sparkPlan: SparkPlan = {
SparkSession.setActiveSession(sparkSession)
// TODO: We use next(), i.e. take the first plan returned by the planner, here for now,
// but we will implement to choose the best plan.
planner.plan(ReturnAnswer(optimizedPlan)).next()
}
//prepareForExecution阶段
// executedPlan should not be used to initialize any SparkPlan. It should be
// only used for execution.
lazy val executedPlan: SparkPlan = prepareForExecution(sparkPlan)
//execute阶段
/** Internal version of the RDD. Avoids copies and has no schema */
lazy val toRdd: RDD[InternalRow] = executedPlan.execute()
......其他代码
}
各ステージでレイジーレイジーロードを使用していることは注目に値します。これに興味がある場合は、前回の記事「Scala関数型プログラミング(6)レイジーロードとストリーム」をご覧ください。
上記では主にSpark SQLモジュールの内容、その背景、主な問題を紹介しています。次に、Dataframe APIの内容と、Spark SQL解析SQL Catalystの内部フレームワークを簡単に紹介します。フォローアップでは、主にCatalystの各ステップのプロセスを紹介し、ソースコードと組み合わせていくつかの分析を行います。
上〜