# 通知設計書 37-SparkListenerSQLExecutionStart

## 概要

本ドキュメントは、Apache SparkにおけるSparkListenerSQLExecutionStartイベント通知の設計について記述する。本イベントはSQL実行が開始された際に発火するイベントであり、Spark SQL UIの中核となる情報を提供する。

### 本通知の処理概要

SparkListenerSQLExecutionStartは、DataFrame/Dataset APIやSQLクエリの実行が開始された際に発火するイベント通知である。SQLExecutionオブジェクトのwithNewExecutionId0メソッド内で生成され、実行計画やメタデータを含む。

**業務上の目的・背景**：Spark SQLの実行をトラッキングし、SQL UIタブでの可視化を可能にするための基盤イベントである。各SQL実行に一意のexecutionIdを割り当て、関連するジョブやステージとの紐付けを行う。開発者やデータエンジニアがSQL実行のパフォーマンス分析やデバッグを行う上で不可欠な情報を提供する。

**通知の送信タイミング**：SQLExecution.withNewExecutionId0メソッド内で、実行計画の生成後（通常パス）またはエラー発生時にイベントがpostされる。通常パスでは物理実行計画の記述とSparkPlanInfoが含まれるが、エラーパスでは空の計画情報が含まれる。

**通知の受信者**：LiveListenerBusに登録された全てのSparkListenerInterface実装。特にSQLAppStatusListener（SQL UI用）が主要な受信者であり、onOtherEventを通じて受信する。

**通知内容の概要**：executionId、rootExecutionId、実行の説明文、詳細（コールサイト情報）、物理実行計画の記述、SparkPlanInfo（計画のツリー構造）、開始時刻、変更された設定、ジョブタグ、ジョブグループID、クエリIDが含まれる。

**期待されるアクション**：SQLAppStatusListenerがイベントを受信し、KVStoreに実行データを書き込む。SQL UIタブで実行の一覧と詳細（実行計画のDAGビジュアライゼーション含む）が表示される。

## 通知種別

アプリ内通知（Sparkイベントバスによるインプロセス非同期イベント配信。SparkListenerBusのonOtherEvent経由）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（LiveListenerBus経由、onOtherEventにディスパッチ） |
| 優先度 | 高（SQL UI表示の基盤） |
| リトライ | なし |

### 送信先決定ロジック

SparkListenerBus.doPostEventのデフォルトケース（`case _ => listener.onOtherEvent(event)`）によりonOtherEventに配信される。SQLAppStatusListenerのonOtherEvent内でパターンマッチされ、onExecutionStartが呼び出される。

## 通知テンプレート

### イベントオブジェクト

| 項目 | 内容 |
|-----|------|
| イベントクラス | `SparkListenerSQLExecutionStart` |
| パッケージ | `org.apache.spark.sql.execution.ui` |
| 親クラス | `SparkListenerEvent` |
| シリアライズ形式 | JSON（JsonProtocol経由） |

### イベントデータ構造

```scala
@DeveloperApi
case class SparkListenerSQLExecutionStart(
    executionId: Long,
    rootExecutionId: Option[Long],
    description: String,
    details: String,
    physicalPlanDescription: String,
    sparkPlanInfo: SparkPlanInfo,
    time: Long,
    modifiedConfigs: Map[String, String] = Map.empty,
    jobTags: Set[String] = Set.empty,
    jobGroupId: Option[String] = None,
    queryId: Option[UUID] = None)
  extends SparkListenerEvent
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| executionId | SQL実行の一意ID | SQLExecution.nextExecutionId | Yes |
| rootExecutionId | ルート実行ID（ネストされたクエリの場合は親のID） | sc.getLocalProperty(EXECUTION_ROOT_ID_KEY) | Yes |
| description | 実行の説明（SQLテキストまたはコールサイト短縮形） | sc.getLocalProperty(SPARK_JOB_DESCRIPTION)またはcallSite.shortForm | Yes |
| details | 詳細（コールサイトのロングフォーム） | callSite.longForm | Yes |
| physicalPlanDescription | 物理実行計画のテキスト表現 | queryExecution.explainString(planDescriptionMode) | Yes |
| sparkPlanInfo | 実行計画のツリー構造 | SparkPlanInfo.fromSparkPlan(queryExecution.executedPlan) | Yes |
| time | 開始時刻（ミリ秒） | System.currentTimeMillis() | Yes |
| modifiedConfigs | セッションで変更された設定 | sparkSession.sessionState.conf.getAllConfs（リダクト済み） | No |
| jobTags | ジョブタグの集合 | sc.getJobTags() | No |
| jobGroupId | ジョブグループID | sc.getLocalProperty(SPARK_JOB_GROUP_ID) | No |
| queryId | クエリの一意ID（UUID v7） | queryExecution.queryIdまたは新規生成 | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | Dataset.collect, DataFrame.show等のアクション実行 | SQLExecution.withNewExecutionId0が呼ばれる | 通常パス: 実行計画生成後にpost |
| API呼び出し | クエリ実行前のエラー | SQLExecution.withNewExecutionId0でLeft(e)パスに入る | エラーパス: 空の計画情報でpost |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| なし | withNewExecutionId0が呼ばれれば必ずイベントが発行される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Dataset.collect等のアクション] --> B[SQLExecution.withNewExecutionId]
    B --> C[withNewExecutionId0]
    C --> D[executionId生成・ローカルプロパティ設定]
    D --> E[startEventオブジェクト生成]
    E --> F{body実行}
    F -->|Right: 通常パス| G[物理実行計画取得]
    G --> H[startEvent.copy with planDesc, planInfo]
    H --> I[sc.listenerBus.post startEvent]
    F -->|Left: エラーパス| J[sc.listenerBus.post startEvent 空の計画]
    I --> K[body実行]
    J --> L[例外をthrow]
    K --> M[SQLAppStatusListener.onExecutionStart受信]
    M --> N[SparkPlanGraph生成・KVStore書込]
    N --> O[LiveExecutionData更新]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（RDBMSは使用しない）

### KVStore書込仕様

| ストア | キー | 用途 | 備考 |
|--------|------|------|------|
| ElementTrackingStore | executionId | SparkPlanGraphWrapper書込 | 実行計画のグラフ構造 |
| ElementTrackingStore | executionId | LiveExecutionData書込 | SQL実行のメタデータ |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 実行計画生成失敗 | queryExecution.executedPlanで例外 | SparkPlanInfo.EMPTYを使用して続行 |
| イベント配信失敗 | ListenerBusキュー溢れ | LiveListenerBusのドロップポリシーに従う |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0 |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし

## セキュリティ考慮事項

- descriptionフィールドはSQL_EVENT_TRUNCATE_LENGTHで切り詰められる
- descriptionフィールドはstringRedactionPatternでリダクション（機密情報マスキング）が適用される
- modifiedConfigsはsessionState.conf.redactOptionsによりリダクション済み
- ドライバプレフィックス・Executorプレフィックスの設定はmodifiedConfigsから除外される

## 備考

- 本イベントはSparkListenerBusの標準パターンマッチではなくonOtherEvent経由で配信される（SQLモジュール固有のイベントのため）
- executionIdはアプリケーション内でグローバルにインクリメントされるAtomicLongから生成される
- rootExecutionIdはネストされたSQL実行（サブクエリ等）を追跡するために使用される
- queryIdはUUID v7形式であり、初回実行時はqueryExecution.queryIdを使用し、再実行時は新規生成される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SQLListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLListener.scala` | 46-66行目: SparkListenerSQLExecutionStartのcase class定義。多数のフィールドを持つリッチなイベント |
| 1-2 | SQLListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLListener.scala` | 33-37行目: SparkListenerSQLAdaptiveExecutionUpdateの定義（関連イベント） |

**読解のコツ**: `@JsonDeserialize(contentAs = classOf[java.lang.Long])`アノテーションは、rootExecutionIdのOption[Long]のJSONデシリアライズ時にIntからLongへの自動変換を行うためのもの。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SQLExecution.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/SQLExecution.scala` | 90-272行目: withNewExecutionId0メソッド。イベント生成・配信の起点 |

**主要処理フロー**:
1. **98行目**: `val executionId = SQLExecution.nextExecutionId`で一意IDを生成
2. **100-105行目**: queryIdの決定（初回実行かどうかで分岐）
3. **153-165行目**: startEventオブジェクトの生成（空の計画で初期化）
4. **169行目**: エラーパス: `sc.listenerBus.post(startEvent)`
5. **185-186行目**: 通常パス: `sc.listenerBus.post(startEvent.copy(physicalPlanDescription = planDesc, sparkPlanInfo = planInfo))`

#### Step 3: リスナー側の処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SQLAppStatusListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLAppStatusListener.scala` | 438-445行目: onOtherEvent内でSparkListenerSQLExecutionStartをパターンマッチ |
| 3-2 | SQLAppStatusListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLAppStatusListener.scala` | 345-370行目: onExecutionStartメソッド。KVStoreへの書込処理 |

**主要処理フロー**:
1. **349行目**: `SparkPlanGraph(sparkPlanInfo)`で計画グラフを構築
2. **354-357行目**: `SparkPlanGraphWrapper`をKVStoreに書込
3. **360-369行目**: LiveExecutionDataにメタデータを設定して更新

#### Step 4: イベント配信経路を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SparkListenerBus.scala | `core/src/main/scala/org/apache/spark/scheduler/SparkListenerBus.scala` | 100行目: `case _ => listener.onOtherEvent(event)`でSQL固有イベントがonOtherEventにルーティング |

### プログラム呼び出し階層図

```
Dataset.collect() / DataFrame.show() 等
    |
    +-- SQLExecution.withNewExecutionId()
            |
            +-- withNewExecutionId0()
                    |
                    +-- nextExecutionId (AtomicLong)
                    +-- queryId決定
                    +-- startEvent生成
                    +-- queryExecution.explainString()
                    +-- SparkPlanInfo.fromSparkPlan()
                    +-- sc.listenerBus.post(startEvent)
                            |
                            +-- SparkListenerBus.doPostEvent()
                                    |
                                    +-- listener.onOtherEvent()
                                            |
                                            +-- SQLAppStatusListener.onOtherEvent()
                                                    |
                                                    +-- onExecutionStart()
                                                            |
                                                            +-- SparkPlanGraph()
                                                            +-- kvstore.write(graphToStore)
                                                            +-- LiveExecutionData更新
```

### データフロー図

```
[入力]                          [処理]                              [出力]

QueryExecution            SQLExecution                     SparkListenerSQLExecutionStart
(executedPlan,      ---> .withNewExecutionId0()     --->   イベント
 sparkSession,           実行計画取得・イベント生成             |
 callSite)                                                  v
                                                     LiveListenerBus
                                                            |
                                                     +------+------+
                                                     |             |
                                              SQLAppStatus    EventLogging
                                              Listener        Listener
                                                     |
                                                     v
                                              KVStore (SQL UI)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SQLListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLListener.scala` | ソース | イベントクラス定義 |
| SQLExecution.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/SQLExecution.scala` | ソース | イベント生成・配信の起点 |
| SQLAppStatusListener.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/ui/SQLAppStatusListener.scala` | ソース | イベント受信・KVStore書込 |
| SparkListenerBus.scala | `core/src/main/scala/org/apache/spark/scheduler/SparkListenerBus.scala` | ソース | イベントディスパッチ（onOtherEvent経由） |
| AdaptiveSparkPlanExec.scala | `sql/core/src/main/scala/org/apache/spark/sql/execution/adaptive/AdaptiveSparkPlanExec.scala` | ソース | AQE実行時の関連イベント発行 |
| EventLoggingListener.scala | `core/src/main/scala/org/apache/spark/scheduler/EventLoggingListener.scala` | ソース | イベントログへの永続化 |
