👀

バッチ監視のプラクティス

はじめに

定期実行するバッチ処理というのは大小様々ありつつ至るところで見られるものだと思います。
実際にフォルシアでも、主にデータの取り込み・加工・出力の処理を中心にいろいろなプロダクトの"重要な機能"として存在しています。
そんな大事なバッチ処理ですが「毎日ちゃんと回って当たり前」と思っていると、いざ止まった時に大きな障害につながりかねません。
しかも、単に「エラーが出てないか」「終わったかどうか」だけ見ていても安心できないのがバッチ監視の難しいところです。

本記事では、フォルシアのバッチ処理の監視の観点を整理しつつ、どのように実践しているかをご紹介します。
以下では、定期実行するバッチ処理の一つについて単に「ジョブ」と表記します。

前提

フォルシアでは以下の技術を多く用いており、この前提でのバッチ処理監視の実践となります。

  • ジョブ実行環境(ほぼすべて AWS 上で動いています)

    • ECS on Fargate
    • EKS on EC2
    • 一部 EC2 サーバー上で直接動作するアプリケーション
  • ワークフローエンジン(ジョブをスケジュール・制御する役割のもの)

  • 監視基盤

    • Prometheus + Alertmanager
    • AWS CloudWatch
    • 一部 内製の監視ツール(統一された IF でアプリ状態を開示する CGI + ECS タスクとしてそれをスクレイピングして異常検知・通知するアプリ)

1. 正常終了の確認

ポイント

ジョブが最後まで走り切り、想定通りに終了コード 0 を返していることを確認するのは、最も基本的な監視です。
一般的には、ジョブ実行環境やワークフローエンジンと連携し終了ステータスを自動収集して通知などに利用します。また、ジョブの成功/失敗率をダッシュボードで可視化するのも分かりやすい方法です。

実践方法

フォルシアでは一回のジョブ失敗の影響が大きいため、失敗に対して都度対応が必要なケースが多いです。そのため成功率などを継続的に集計することはあまりなく、ワークフローエンジンの機能以外で、追加の仕組みを作ってまで終了ステータスを収集することはしていません。ジョブ成功はログに残すだけとし、必要に応じて確認できる形を取っています。さらに頻繁に実行されるジョブも多いため、正常終了の通知は煩雑になりがちであり、多くの場合は通知していません。


2. 異常終了の検知

ポイント
ジョブがエラーコードを返したり途中で落ちたりするケースでは、即座に検知することが重要です。
検知方法としては、終了コードを拾う方法のほか、"ERROR" や "FATAL" といったログ出力を正規表現でキャッチする手法もあります。長時間実行するジョブでは、リアルタイムでのログ集約と監視が必要になる場合もあります。
特定の失敗ケースをログ出力でキャッチするには、当然ながらジョブ実装の時点でログ形式を取り決めるべきです。

実践方法

ワークフローエンジンの終了コードによる異常終了検知機能を利用し、組み込みの exit handler で Slack へ速報通知を流しています。一方、組み込み機能がない社内製ワークフローエンジンや 一部の crontab 実行では、「異常終了した状態」を表すファイルを作成し、それを独自の exporter で公開して Prometheus などによる検知を行っています。
また、リアルタイムログ監視については以下の仕組みを活用しています。

  • CloudWatch Logs のメトリクスフィルター + CloudWatch アラーム
  • 簡易的にログストリームをプログラムで監視する仕組みをワークフローに組み込む場合もあり
  • k8s 環境では grok_exporter + Prometheus

3. 想定通りに実行できていないケースの検知

ポイント

ジョブが「正常終了」となっても、実際には処理件数が 0 件だったり、出力ファイルが欠落していたりと成果物が想定と違う場合があります。こうした「サイレント障害」を防ぐには、終了コードとは別に成果物検証の観点での監視が必要です。

よく行われる自動化としては、次のようなものがあります。

  • データ件数チェック(過去平均値や閾値との比較)
  • ファイル存在チェック(期待される出力ファイルの有無や形式確認)
  • データ品質チェック(NULL 割合、型の整合性、外部キー制約など)
  • データ鮮度チェック(更新日時など)

実践方法

ジョブ内にデータ件数・鮮度チェックを組み込み、一定閾値や過去件数からの割合で異常を検知する仕組みを導入しています。異常なデータとなりそうな場合には明示的にエラー終了したり、当該処理のみをスキップして通知する運用を行っています。


4. 想定以上の時間がかかるケースの検知 - タイムアウトの検知

ポイント

正常終了/異常終了の検知だけでは、ジョブが想定以上に時間を要してしまったり、最悪の場合「実行中のまま止まっている」ケースを見逃す危険があります。そのためジョブごとに許容実行時間を定義し、タイムアウト時に通知することが重要です。

実装方法は様々ですが、実装レベルのライブラリや実行基盤、ワークフローエンジンのレイヤーそれぞれで実現可能です。ただし最悪のケースを救うためには、バッチ処理全体の開始・終了を制御するワークフローエンジン側で必ずタイムアウトを設定するべきだと考えています。

実践方法

ジョブを実行するワークフローエンジンでのタイムアウト機能と exit handler での通知を組み合わせています。ジョブ内の個々の実装でもタイムアウトとエラーハンドリングを行うこともあり、この場合はジョブの異常終了として検知されます。また、ジョブ同士の排他制御により多重起動をエラーとする仕組みがあります。これにより実質的に「次のジョブまでの実行間隔 = タイムアウト値」となっています。
加えて、DB の更新日時カラムやログファイルの更新有無を外から確認するいわゆるハートビート監視のような仕組みを用意して、スケジュール通りの実行を担保しているケースもあります。


5. リソース使用量の監視

ポイント

ジョブは時として「データが想定より多かった」「処理ロジックが非効率だった」などの理由でメモリや CPU やストレージなどのリソースを大量消費し、問題を引き起こす場合があります。このため常時稼働するサービスと同様にバッチ処理でもリソース消費の監視が必要であり、特にクラウド環境ではコスト増加を早期発見したりプロビジョニング見直しを行うためにも重要です。

実践方法
以下のシンプルな方法を用いています。

  • EC2 や ECS では CloudWatch メトリクスを利用
  • EKS では cadvisor や node-exporter によるメトリクス公開 + Prometheus での収集

ただし現状は、データは集められているものの都度振り返る程度にとどまっており、効果的なアラートや将来予測にまでは活用しきれていない部分があります。


6. アラートに気づかせるための工夫

ポイント
ジョブの異常終了やタイムアウトは通常一回だけの出来事です。そのため単発で通知しても、タイミング次第で誰も気づけない恐れがあります。そこで「異常状態を保持し続け、対応されるまで通知し続ける」ことを基本としています。

実践方法

フォルシアでは異常終了した状態をファイルとして保持し、それをメトリクス化して外部から監視して、アラートを通知し続けています。また、最近では CloudWatch アラームが出た時点で DynamoDB に異常状態を保持し、その状態を Prometheus で監視する仕組みも導入しました。
通知は Slack チャンネルが基本ですが、クリティカルな場合はオンコール体制として担当者の電話が鳴り続けるようになっています。


まとめ

定期実行するバッチ処理の監視は、単に「終了を確認する」だけでは不十分なことが多いです。

  • 正常終了・異常終了の監視: 基本的な実行状態の把握
  • 実行中のリアルタイム監視: 長時間バッチでの早期問題発見
  • 想定通りに動いているかの検証: データ品質・件数・出力ファイルのチェック
  • タイムアウト検知: 無限ループや処理停止の検出
  • リソース使用量の監視: システム負荷とコスト管理
  • 状態を保持した継続的な通知: 見逃し防止のための継続アラート

どれも言われてみれば当たり前という気はしますが、特に タイムアウト検知 は私自身の失敗経験からも強調したい観点です。外部システムとの通信が不安定で、ジョブが終了もせずエラーも出さないまま止まっていたことがあり、改めてその重要性を痛感しました。

フォルシアではこれらを組み合わせることで「問題に気づける監視」を実現しています。
ただし、アプリごとに別々の仕組みを構築している部分の標準化や、「問題を未然に防ぐ監視」などは今後の課題であり、日々改善していきたいと思います。

この記事が、皆さんの定期実行バッチ監視設計の見直しに少しでも役立てば幸いです。

この記事を書いた人

三浦 柾
2020 年新卒入社
最近テニスにはまりすぎて、所かまわず素振りしちゃうのが悩みです。

FORCIA Tech Blog

Discussion