# 通知設計書 71-AlterPartitionsEvent

## 概要

本ドキュメントは、Apache Sparkの外部カタログ（ExternalCatalog）において、テーブルのパーティションが変更された後に発火されるイベント通知「AlterPartitionsEvent」の設計を記述する。

### 本通知の処理概要

AlterPartitionsEventは、外部カタログに登録されたテーブルのパーティション情報が変更された後に発火されるイベント通知である。Hiveメタストアなどの外部カタログを通じてパーティションの属性（ストレージ情報、パラメータなど）が変更された際に、登録されたリスナーに対して変更完了を通知する。

**業務上の目的・背景**：データレイクやデータウェアハウスの運用において、テーブルパーティションのメタデータ管理は重要な課題である。パーティションのストレージパス、シリアライゼーション設定、カスタムパラメータなどが変更された際に、依存するコンポーネント（キャッシュ管理、統計情報更新など）に変更を通知し、データの整合性を維持する必要がある。AlterPartitionsEventは、この変更通知メカニズムの「事後通知」として機能する。

**通知の送信タイミング**：ExternalCatalogWithListenerの`alterPartitions`メソッドにおいて、デリゲート先の外部カタログでパーティション変更処理が正常に完了した直後に発火される。変更前にはAlterPartitionsPreEventが先行して発火される。

**通知の受信者**：ExternalCatalogEventListenerインタフェースを実装し、ExternalCatalogWithListenerに登録されたすべてのリスナーが受信する。主にSpark内部のカタログキャッシュ管理コンポーネントやメタデータ監査ログ機構が対象となる。

**通知内容の概要**：変更対象のデータベース名（database）、テーブル名（name）、および変更されたパーティションの仕様一覧（partSpecs: Seq[TablePartitionSpec]）が含まれる。TablePartitionSpecはMap[String, String]型のエイリアスであり、パーティションカラム名と値のペアで構成される。

**期待されるアクション**：リスナーは通知を受けて、パーティションに関連するキャッシュの無効化、統計情報の再計算トリガー、メタデータ変更の監査ログ記録などを実行することが期待される。

## 通知種別

アプリ内通知（Sparkリスナーバスを通じたイベント通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（ListenerBus.postToAll による同期配信） |
| 優先度 | 中 |
| リトライ | 無し（同期配信のため） |

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

ExternalCatalogWithListenerに登録されたすべてのExternalCatalogEventListenerに対して一斉配信される。ListenerBusのpostToAllメソッドにより、登録済みリスナー全件に対して順次`onEvent`メソッドが呼び出される。

## 通知テンプレート

### メール通知の場合

該当なし。本イベントはSpark内部のリスナーバスを通じたプログラム内通知であり、メール通知は行わない。

### 本文テンプレート

```
イベント型: AlterPartitionsEvent
データベース: {database}
テーブル: {name}
パーティション仕様: {partSpecs}
```

### 添付ファイル

該当なし。

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| database | 対象データベース名 | ExternalCatalogWithListener.alterPartitions引数db | Yes |
| name | 対象テーブル名 | ExternalCatalogWithListener.alterPartitions引数table | Yes |
| partSpecs | 変更対象パーティション仕様のリスト | CatalogTablePartition.specから抽出 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | ExternalCatalogWithListener.alterPartitions | デリゲート先の変更処理が正常完了 | パーティション変更操作の完了後に発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| デリゲート先で例外発生 | alterPartitionsの実行中に例外が発生した場合、AlterPartitionsEventは発火されない（AlterPartitionsPreEventのみ発火済み） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[alterPartitions呼び出し] --> B[partSpecs抽出: parts.map 親.spec]
    B --> C[AlterPartitionsPreEvent発火: postToAll]
    C --> D[delegate.alterPartitions実行]
    D -->|成功| E[AlterPartitionsEvent発火: postToAll]
    D -->|失敗| F[例外スロー・Event未発火]
    E --> G[登録リスナーのonEvent呼び出し]
    G --> H[終了]
    F --> H
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| Hiveメタストア（パーティション情報） | パーティションメタデータの変更 | デリゲート先のExternalCatalog実装に依存 |

### テーブル別参照項目詳細

#### Hiveメタストア（パーティション情報）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| パーティション仕様（spec） | 変更対象の特定 | database, table, partSpec指定 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Hiveメタストア（パーティション情報） | UPDATE | パーティション属性の変更 |

#### 送信ログテーブル

該当なし。イベントは永続化されない。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| リスナー例外 | リスナーのonEventメソッドで例外発生 | ListenerBusが例外をキャッチしてログ出力。他のリスナーへの配信は継続 |
| デリゲート例外 | 外部カタログでの変更処理失敗 | AlterPartitionsEventは発火されず、呼び出し元に例外が伝播 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（リトライなし） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし。パーティション変更操作の実行時に即座に配信される。

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

- イベントはSparkプロセス内部でのみ配信され、ネットワークを経由しない
- パーティション仕様にはカラム名と値のペアが含まれるため、パーティションキーに個人情報が含まれる場合はリスナー側での取り扱いに注意が必要
- ExternalCatalogEventListenerの登録はSparkの内部APIであり、一般ユーザーからのアクセスは制限されている

## 備考

- AlterPartitionsEventはPartitionsEventトレイトを継承し、さらにTableEvent、DatabaseEvent、ExternalCatalogEventの階層を持つ
- SparkListenerEventも継承しているため、Sparkの汎用リスナーバスでも受信可能
- 対応するPre版イベント（AlterPartitionsPreEvent）は変更処理の実行前に発火される
- TablePartitionSpecはCatalogTypes.TablePartitionSpecとして定義され、型エイリアスMap[String, String]である

---

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

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

### 推奨読解順序

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

まず、イベントクラスとその継承階層、およびパーティション仕様の型を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | events.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/events.scala` | AlterPartitionsEvent（286-290行目）のcase class定義。PartitionsEvent（211-216行目）、TableEvent（78-83行目）、DatabaseEvent（38-43行目）、ExternalCatalogEvent（26行目）の継承階層を確認 |

**読解のコツ**: Scalaのcase classとtrait継承を理解すること。AlterPartitionsEventは4階層のtrait継承を持ち、database, name, partSpecsの各フィールドはトレイトで定義されている。

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

処理の起点となるExternalCatalogWithListenerのalterPartitionsメソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ExternalCatalogWithListener.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogWithListener.scala` | alterPartitionsメソッド（235-243行目）でのPre/Postイベント発火パターンを理解 |

**主要処理フロー**:
1. **239行目**: `val partSpecs = parts.map(_.spec)` - CatalogTablePartitionからパーティション仕様を抽出
2. **240行目**: `postToAll(AlterPartitionsPreEvent(db, table, partSpecs))` - 変更前イベントを全リスナーに配信
3. **241行目**: `delegate.alterPartitions(db, table, parts)` - デリゲート先で実際の変更を実行
4. **242行目**: `postToAll(AlterPartitionsEvent(db, table, partSpecs))` - 変更後イベントを全リスナーに配信

#### Step 3: リスナーバスの配信メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ExternalCatalogWithListener.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogWithListener.scala` | ListenerBus[ExternalCatalogEventListener, ExternalCatalogEvent]の混合（29行目）とdoPostEvent（34-38行目）の実装 |

**主要処理フロー**:
- **34-38行目**: doPostEventメソッドがlistener.onEvent(event)を呼び出し、各リスナーにイベントを配信

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

```
ExternalCatalogWithListener.alterPartitions(db, table, parts)
    |
    +-- parts.map(_.spec)  -- パーティション仕様抽出
    |
    +-- postToAll(AlterPartitionsPreEvent(db, table, partSpecs))
    |       |
    |       +-- ExternalCatalogEventListener.onEvent(event)  [各リスナー]
    |
    +-- delegate.alterPartitions(db, table, parts)
    |       |
    |       +-- [Hiveメタストア等の実際の変更処理]
    |
    +-- postToAll(AlterPartitionsEvent(db, table, partSpecs))
            |
            +-- ExternalCatalogEventListener.onEvent(event)  [各リスナー]
```

### データフロー図

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

alterPartitions(db,table,parts) --> ExternalCatalogWithListener     --> AlterPartitionsPreEvent
                                        |                               |
                                        v                               v
                                    delegate.alterPartitions        ExternalCatalogEventListener
                                        |                               .onEvent()
                                        v
                                    AlterPartitionsEvent            --> ExternalCatalogEventListener
                                                                        .onEvent()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| events.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/events.scala` | ソース | イベントクラス定義（AlterPartitionsEvent: 286-290行目） |
| ExternalCatalogWithListener.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogWithListener.scala` | ソース | イベント発火元（alterPartitions: 235-243行目） |
| ExternalCatalogEventSuite.scala | `sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogEventSuite.scala` | テスト | イベント発火のテスト |
