# バッチ設計書 14-FsHealthService (FsHealthMonitor)

## 概要

本ドキュメントは、OpenSearchのファイルシステムの書き込み可否を定期的にチェックするバッチ処理「FsHealthService (FsHealthMonitor)」の設計仕様を記載する。

### 本バッチの処理概要

FsHealthServiceは、ノードのデータパスに対して定期的にファイルの書き込みテストを実施し、ファイルシステムが正常に動作しているかを監視するバッチ処理である。内部のFsHealthMonitorがRunnableとして実装され、scheduleWithFixedDelayで定期実行される。

**業務上の目的・背景**：OpenSearchノードはデータをローカルファイルシステムに永続化するため、ファイルシステムの健全性はノードの正常動作に不可欠である。ディスク障害、ファイルシステムのフリーズ、NFSマウントの切断などにより書き込みが不可能になった場合、インデックス操作やトランザクションログの書き込みが失敗する。本バッチは、そのような状態を早期に検出し、該当パスをunhealthyとしてマークすることで、クラスタの健全性監視（NodeHealthService）を通じた適切な対応を可能にする。

**バッチの実行タイミング**：`monitor.fs.health.refresh_interval`設定に基づく定期実行（デフォルト60秒間隔）。FsHealthServiceのdoStart()時にscheduleWithFixedDelayで開始される。

**主要な処理内容**：
1. 各ノードデータパスに対してテンポラリファイルの書き込みテストを実施
2. テンポラリファイルの作成、データ書き込み、fsync、削除を実行
3. 所要時間を計測し、スローパス閾値を超過した場合は警告ログを出力
4. 健全性タイムアウト閾値を超過した場合は該当パスをunhealthyとしてマーク
5. 例外発生時は該当パスをunhealthyとしてマーク
6. メトリクスカウンタ（fsHealth.failure.count）を更新

**前後の処理との関連**：本バッチの結果は、NodeHealthService.getHealth()を通じてクラスタの健全性判定に使用される。ノードのヘルスステータスがUNHEALTHYになると、クラスタマネージャがそのノードをクラスタから除外する判断を行う場合がある。

**影響範囲**：ノードの健全性ステータスに影響する。unhealthyと判定されたパスはNodeHealthServiceを通じてクラスタ全体に伝播する。また、テンポラリファイルの書き込みテストにより微小なI/O負荷が発生する。

## バッチ種別

モニタリング / ヘルスチェック

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 定期実行（デフォルト60秒間隔） |
| 実行時刻 | ノード起動後、設定間隔ごとに繰り返し実行 |
| 実行曜日 | 該当なし（常時） |
| 実行日 | 該当なし（常時） |
| トリガー | ThreadPool.scheduleWithFixedDelay（Names.GENERIC） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| FsHealthServiceがstart済みであること | doStart()でスケジューリングが開始される |
| NodeEnvironmentが利用可能であること | ノードデータパスが取得可能であること |

### 実行可否判定

`monitor.fs.health.enabled`設定がtrueの場合にのみ実際のヘルスチェックを実行する。falseの場合はrun()メソッド内で早期リターンする（スケジューリング自体は継続）。動的に変更可能。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| monitor.fs.health.enabled | boolean | No | true | ヘルスチェックの有効/無効 |
| monitor.fs.health.refresh_interval | TimeValue | No | 60s | ヘルスチェックの実行間隔 |
| monitor.fs.health.slow_path_logging_threshold | TimeValue | No | 5s | スローパス警告ログの閾値 |
| monitor.fs.health.healthy_timeout_threshold | TimeValue | No | 60s | 健全性タイムアウト閾値 |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| NodeEnvironment.nodeDataPaths() | Path[] | ノードのデータパス一覧 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| unhealthyPaths | Set<Path> | unhealthyと判定されたパスのセット |
| brokenLock | boolean | ノードロックが壊れているかのフラグ |
| fsHealth.failure.count | メトリクスカウンタ | ヘルスチェック失敗回数 |

### 出力ファイル仕様

| 項目 | 内容 |
|-----|------|
| ファイル名 | .opensearch_temp_file |
| 出力先 | 各ノードデータパス直下 |
| 文字コード | UTF-8 |
| 内容 | ランダムBase64UUID文字列 |
| 備考 | テスト後に即座に削除される一時ファイル |

## 処理フロー

### 処理シーケンス

```
1. FsHealthMonitor.run()実行
   └─ enabled==falseの場合は早期リターン
2. checkInProgress をtrueに設定
3. monitorFSHealth()呼び出し
   └─ nodeEnv.nodeDataPaths()でデータパス一覧取得
      └─ IllegalStateException発生時: brokenLock=trueに設定して終了
4. 各データパスについてループ
   ├─ パスの存在確認
   ├─ テンポラリファイル(.opensearch_temp_file)の既存チェック・削除
   ├─ テンポラリファイルの新規作成・書き込み・fsync
   ├─ テンポラリファイルの削除
   ├─ 所要時間計測
   ├─ slowPathLoggingThreshold超過時: 警告ログ出力
   ├─ healthyTimeoutThreshold超過時: パスをunhealthySetに追加
   └─ 例外発生時: エラーログ出力、パスをunhealthySetに追加
5. unhealthyPaths を更新（nullの場合は全パス健全）
6. brokenLock = false に設定
7. emitMetric(): UNHEALTHYの場合カウンタをインクリメント
8. checkInProgress をfalseに設定
```

### フローチャート

```mermaid
flowchart TD
    A[FsHealthMonitor.run 開始] --> B{enabled?}
    B -->|No| C[早期リターン]
    B -->|Yes| D[checkInProgress = true]
    D --> E[nodeDataPaths 取得]
    E --> F{IllegalStateException?}
    F -->|Yes| G[brokenLock = true]
    F -->|No| H[各パスをループ]
    H --> I[テンポラリファイル書き込みテスト]
    I --> J{例外発生?}
    J -->|Yes| K[パスをunhealthyに追加]
    J -->|No| L{タイムアウト超過?}
    L -->|Yes| K
    L -->|No| M{slowPath超過?}
    M -->|Yes| N[警告ログ出力]
    N --> O[次のパスへ]
    M -->|No| O
    K --> O
    O --> H
    H --> P[unhealthyPaths更新]
    P --> Q[メトリクス出力]
    Q --> R[checkInProgress = false]
    G --> Q
    C --> S[FsHealthMonitor.run 終了]
    R --> S
```

## データベース操作仕様

### 操作別データベース影響一覧

本バッチはデータベース操作を行わない。ファイルシステムへの一時ファイル書き込み操作のみを行う。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| なし | IllegalStateException | ノードロックが壊れている場合 | brokenLock=trueに設定、UNHEALTHYステータスを報告 |
| なし | Exception | テンポラリファイル書き込み失敗 | エラーログ出力、該当パスをunhealthyに追加 |
| なし | Exception | run()全体での例外 | エラーログ"health check failed"を出力 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 明示的なリトライなし（定期実行により自動再試行） |
| リトライ間隔 | 次回定期実行間隔（デフォルト60秒） |
| リトライ対象エラー | すべてのException |

### 障害時対応

ファイルシステム障害が検出された場合は、unhealthyPathsにパスが設定され、NodeHealthServiceを通じてノードのヘルスステータスがUNHEALTHYとなる。ディスクの物理的な障害や、マウントの問題を調査し、復旧後は次回ヘルスチェックで自動的にHEALTHYに復帰する。

## トランザクション仕様

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | なし（ファイルシステムへの一時書き込みのみ） |
| コミットタイミング | fsync呼び出し時 |
| ロールバック条件 | 該当なし |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | ノードのデータパス数（通常1-3） |
| 目標処理時間 | 5秒以内（slow_path_logging_threshold） |
| メモリ使用量上限 | 微小（UUID文字列のバイト配列のみ） |

## 排他制御

checkInProgressフラグ（AtomicBoolean）により、同時に複数のヘルスチェックが実行されないよう制御される。compareAndSetでtrueに設定し、完了時にfalseに戻す。scheduleWithFixedDelayを使用しているため通常は同時実行されないが、checkInProgressはgetHealth()メソッドでタイムアウト判定にも使用される。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| デバッグログ | ヘルスチェック成功時 | "health check succeeded" |
| 警告ログ | スローパス閾値超過時 | "health check of [{path}] took [{ms}ms] which is above the warn threshold of [{threshold}]" |
| エラーログ | 健全性タイムアウト超過時 | "health check of [{path}] failed, took [{ms}ms] which is above the healthy threshold of [{threshold}]" |
| エラーログ | 書き込みテスト失敗時 | "health check of [{path}] failed" + 例外詳細 |
| エラーログ | ノードロック破損時 | "health check failed" + IllegalStateException詳細 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| fsHealth.failure.count | 増加傾向 | MetricsRegistryカウンタ |
| ヘルスチェック所要時間 | monitor.fs.health.slow_path_logging_threshold（デフォルト5秒） | OpenSearchログ |
| ヘルスステータス | UNHEALTHY | NodeHealthService経由 |

## 備考

- FsHealthMonitorはFsHealthService内部のインナークラスとして実装されている
- 実装ファイル: `server/src/main/java/org/opensearch/monitor/fs/FsHealthService.java`（195行目付近）
- FsHealthServiceはAbstractLifecycleComponentを継承し、NodeHealthServiceインターフェースを実装している
- テンポラリファイル名は`.opensearch_temp_file`固定
- 書き込みテストのデータはコンストラクタ時に生成されるランダムBase64UUID文字列
- monitor.fs.health.enabled、slow_path_logging_threshold、healthy_timeout_thresholdは動的に変更可能
- getHealth()メソッドは、checkInProgressフラグとhealthyTimeoutThresholdを組み合わせて、ヘルスチェックが長時間実行中の場合もUNHEALTHYと判定する
