Datadogによるバッチの監視

本記事は リクルートライフスタイル Advent Calendar 2019 9日目の記事です。

ホットペッパーグルメレストランボードのSREを担当している明智です。

本記事では、バッチの監視を改善した事例について紹介します。

バッチの監視に必要なこととは

監視は何のために行うのでしょうか?
私は、異常な状態を検知するためだと考えています。

バッチの監視には、以下の3つの異常な状態を検知する必要があります。

  • 起動しない状態
  • 異常終了した状態
  • 実行が終わらない状態

バッチの監視状況

改善着手前の監視状況は以下の通りでした。

監視項目 監視状況 監視方法
起動しない状態 一部の主要なバッチに対して監視できている 各バッチの実行時刻をDBに保持して、その時刻をチェックするバッチを定期的に実行することで起動しているかを監視
異常終了した状態 全てのバッチに対して監視できている エラーが発生して異常終了した場合は、メールで通知
実行が終わらない状態 監視できていない

実行が終わらない状態の監視方法

改善内容としては、実行が終わらない状態を監視できるようにすることにしました。

まずはじめに考えたのは、起動しない状態の監視と同様の方法です。
各バッチの実行時間の閾値をDBに保持して、閾値を超えていないかチェックするバッチを定期的に実行することで、実行時間が長くなっているバッチを検知する方法です。
しかしながら、この方法では閾値の変更に本番DBへのアクセスが必要になったり、通知先の変更にバッチの改修が必要になったりと変更ハードルが高くなってしまいます。

そこで次に考えたのは、Datadogを使った監視方法です。
システム監視にはすでにDatadogを導入しており、バッチの監視にも導入することで通知設定の集約と柔軟性にメリットがあると感じました。

実行時間をDatadogに送る方法

カスタム Agent チェックを作成することで、一定の間隔でバッチの実行時間をDatadogに送ることができます。

担当サービスでは、JP1でバッチの管理を行なっています。
JP1のコマンドから実行中のバッチの実行時間を取得して、Datadogにメトリクスとして送信しています。

architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# coding: utf-8
import os
import subprocess
from checks import AgentCheck
from datetime import datetime
__version__ = "1.0.0"
class JP1Check(AgentCheck):
    def check(self, instance):
        # 論理ホストを参照する場合は、環境変数JP1_HOSTNAMEを設定
        hostname = instance.get('jp1_hostname')
        if hostname:
            os.environ['JP1_HOSTNAME'] = hostname
        # 実行中のジョブネットを取得
        cmd = '/opt/jp1ajs2/bin/ajsshow -F {} -f"%j%t%C%t%s" -ga -R -T {}'.format(instance.get('service_name'), instance.get('jobnet_path'))
        p1 = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
        p2 = subprocess.Popen(['grep', 'running'], stdin=p1.stdout, stdout=subprocess.PIPE)
        p1.stdout.close()
        output = p2.communicate()[0]
        # Datadogにメトリクスを送信
        for line in output.splitlines():
            # "ジョブネット名\tジョブネットの状態\tジョブネットの実行開始日時"
            jobnet = line[1:-1].split("\t")
            exectime = datetime.now() - datetime.strptime(jobnet[2], "%b %d %Y %H:%M:%S")
            tags = [
                'jobnet:{}'.format(jobnet[0])
            ]
            self.gauge('jp1.exectime', exectime.total_seconds(), tags=tags)

実行時間が長くなっている時の通知方法

Datadogのアラート設定機能で、各バッチに対して閾値と通知先を設定します。

導入した結果わかったこと

バッチの実行時間が長くなっていることは、自動で検知することが可能になりました。
しかしながら、運用してみると問題があることがわかりました。

検知はできるようになりましたが、検知した事象が即日対応が必要なものなのか、後日対応でよいものなのかが通知内容からはわからない状況でした。
例えば、一時的な処理件数の増加による数分の処理遅延は事象の認識ができていれば後日対応でよいですが、インフラ障害やプログラムの異常によるプロセスのゾンビ化などは即日対応が必要になります。

改善案

Datadogのアラート設定ではWarningとAlertの2種類の閾値を設定することができるので、それぞれの閾値を使い分けることで上記の問題を解消できるのではないかと考えています。
Warningには後日対応でも許容できる閾値を設定し、Alertには即日対応が必要な閾値を設定します。
これにより、通知が来た際の対応方法が明確になります。

まとめ

本記事では、バッチの実行が終わらない状態をDatadogを使って検知する方法を紹介しました。
本件を通じて、検知後の対応も見据えた運用設計の重要性を改めて感じました。