謎のバッチサーバを渡された時の切り崩し方


この記事は[ぷりぷりあぷりけーしょんず Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ppap)の24日目の記事です。
またしてもちょっと遅刻しました。

# はじめに
クリスマスの雰囲気が腹立たしいので泥臭い調査系の記事でも書きます。

皆さんは普段お仕事をしていて「よくわからないバッチがたくさん動いているサーバ」を引き継ぐことってないでしょうか？
私はよくあります。別に引き継ぎの時だけではなく既存の処理に改修を入れる際、後続のバッチに影響が無いか調査が必要で仕方なく謎のバッチサーバを解析することもあると思います。

この手の調査は勘所をおさえていないとどう進めて良いか途方に暮れるので未来の自分のネタ帳も兼ねて書き留めておきます。

# 切り崩し方
実際に謎バッチサーバを切り崩していきます。
いつもやっている方法を思いつくままに書いていきます。
他にも思い出したら書き足そうと思います。

## 知ってそうな人に話を聞く
その現場に長くいる人にとりあえず何か知らないかヒアリングしましょう。
ここで有力な情報を聞ければその後の調査がかなり楽になる可能性があります。

* 他に同じような処理はないか？
* 現行サービスのどの機能に関わるバッチなのか？
* 定期実行されているのか手動で動かしているのか？
* 教えていただいた情報の確度はどの程度なのか？（ほぼ間違いない。多分そう。もしかしたら～かも？　ないと思うけど～の可能性がある。伝説によると～と言われている。とかとか）

などを聞いていきます。
たずねておいて確度を聞くと何だか失礼な気もしますが、全てのヒアリング内容を正しいものと思っていると先入観によってその後の調査で苦労することがあります。
横柄にならないようにやんわりとどの程度確かな情報かお聞きしましょう。

## 運用資料に目を通す
運用資料と言う名のバッチ実行手順書や定期実行スケジュール表のようなものがあったらラッキーです。
運用資料を読む目的は大きく分けて二つあり

* バッチの実行はどこから始まるか特定する
* 何種類の処理が連結したバッチなのか知る

ことが目的となります。
実行手順書の場合は現場によりますが社内 wiki や GitLab の Readme に書かれている場合が多いです。
定期実行スケジュールの場合は cron、Airflow、digdag、Rundeck、Azkaban など実際にタスクを定義しているミドルウェアのタスク定義を確認しましょう。
cron であれば

```
/var/spool/cron/user
/etc/crontab
/etc/cron.hourly
/etc/cron.daily
/etc/cron.monthly
/etc/cron.weekly
/etc/cron.d
```
あたりに定義が書かれています。
その他のミドルウェアの場合はそれぞれに従ってください。

起動用のシェルスクリプトなどが発見できれば『バッチの実行はどこから始まるか特定する』という目的達成です。
実行されるバッチの起動スクリプトを時系列順に全て並べれば『何種類の処理が連結したバッチなのか知る』も達成です。

### 起動スクリプトの依存関係をまとめる
起動スクリプトは場合によっては複数の起動スクリプトをラップして順番に実行する言わば「連続起動スクリプト」や引数でどのバッチを実行するか指定することができる「なんちゃって起動 Util」などの「起動スクリプトの中でさらに別の起動スクリプトを呼ぶ」ような依存構造を持つ場合があります。
これらの場合依存関係を頭で覚えておくのは非常に難しいので、以下の例のようにツリー構造でメモって wiki などに残しておきましょう。

```
例）hoge.sh の中で foo.sh と bar.sh が呼ばれている場合
/home/develop/hoge.sh
├ /home/develop/foo.sh
└ /home/develop/bar.sh
```

## ソースコードに軽く目を通す
起動スクリプトを追っていくとそのうち Java、Ruby、Python などのプログラミング言語で書かれたバッチ処理の本丸にたどり着くはずです。
多くの謎バッチの場合、その内部品質はあまり良くないのでガッツリ読むと疲弊します。
なのでポイントを絞って読む必要があります。
私が普段重視しているポイントは以下の三点

* プログラムの IN/OUT
* お前要は何してるの？
* ログどこに出してるの？

### IN/OUT をまとめる
バッチ処理はほぼ例外なく「入力、加工、出力」に分けることができます。
このうち「入力」と「出力」がわかれば試しに適当なデータを食わせて実行ができるので入出力が書いてありそうな部分を重点的に調べます。
`～Reader` 、`～Writer` などのクラス名があればかなり怪しいです。

入出力のパターンは多岐に渡り、パッと思いつく限りでも

* バッチサーバ上のファイル（TSV、CSV、XML など）
* GCS、S3 などのクラウドストレージ上のファイル（TSV、CSV、XML、parquet など）
* データベース（RDB、NoSQL、DWH など）
* 外部 API（Json、XML など）
* 外部サイトのスクレイピング結果（入力）
* メール（主にメール送信バッチの出力先）

などが挙げられます。
このうちバッチサーバ上のファイルやスクレイピング結果以外は開発環境・本番環境で違うプロジェクト・ホストになっていることがほとんどだと思われるので何らかの設定ファイルに記載されている場合が多いです。
使っているフレームワークなどにもよりますが application.conf など設定ファイルっぽいファイルを確認しましょう。

設定ファイル以外の方法だと Linux の環境変数に DB の接続先ホスト名などを持たせているパターンもあり得ます。
この場合はバッチの実行前に `export` コマンド等で環境変数を設定しているはずなのでそれを探しましょう。

これでバッチがどこに対して入出力しているかが判明します。

### 何してるか一言で表す
IN/OUT までわかればもう少し詳しく読めば具体的に何をしているかが何となくわかると思います。
「～ファイルを読み込んで～DB へ書き込む処理」「～DB からデータを読み込んでメールを送信する処理」などの粒度で良いので各バッチが何をしているか一言でまとめます。

### ログ出力先を特定
バッチ処理は障害などが起こって調査する際実行ログが命綱です。
障害調査でなくともログから得られる情報はたくさんあります。
ログがどこに出力されているか確認しましょう。
もし万が一まともにロギングされていないようなバッチの場合はいっそ捨てた方が幸せだと思います（暴論）

## 開発環境等で動かしてみる
IN/OUT が分かっているのでダミーデータを作って動きを確認してみます。
言わずもがな必ず開発環境で実行しましょう。本番環境を使おうもんなら多分めっちゃ怒られます。下手すりゃ一発退場です。
ここでの実行は特定のロジックが正しく実装されていることを検証することが目的ではありません。
「A というデータを食わせたら B という出力結果になった」
という事実を得ることが目的です。

### 出力先を見る
バッチ処理によって出力される DB やファイルを確認します。
ダミーデータを自分で作っているので中身のロジックが何となく予測できるはずです。

### ログを見る
自分が実行した分のログがどれかわかるので内部動作を予測できます。
エラーが出た場合でもその原因等が出力されているはずなので仕様理解に役立ちます。

### バッチを実行する際の注意点
開発環境とは言え油断すると「やらかす」ので注意しましょう。
主に注意する点は以下の通り。

#### 他の人に驚きを与えちゃう
開発チーム全体で共用の開発環境の場合バッチ処理の結果、DB に予期せぬレコードができて開発環境を使っている他のメンバーに驚きを与える可能性があります。
開発環境ならそれくらいの不整合は当然起こりえますが、同じ環境を使っているメンバーにバッチのテスト中だと予め伝えておくのが無難です。

#### 実際のユーザーにメール送っちゃう
ダミーデータを作る際に本番環境などのデータを抜いて作る場合、実在するユーザーのメールアドレス等を抜いて来ちゃってメール送信バッチを実行しちゃうとかのパターンです。
そもそもメールアドレスをマスキングもせずに抜いてくること自体大問題ですが、どうしても必要でデータを抜いてくる場合は

* 無効なアドレスに書き換える
* メールが飛ばないように SMTP のポートを閉じる

など万が一にも実在ユーザーにメールが飛ばないようにしましょう。

#### 外部サービスの負荷を上げちゃう
外部 API やスクレイピングでデータを取得してくるバッチの場合無節操に実行すると実質 F5 アタックをしかけているのと同義になります。
マルチスレッド等で外部サービスを使うバッチの場合は注意して実行しましょう。

## 詳細なロジックの流れを書き出す
ここまで実行すれば大分謎バッチの中身が予測できているはずなのでソースコードをある程度読み込めるようになっているはずです。
出力が発生する条件、例外時の挙動など詳しいロジックを UML などでまとめましょう。
ちなみに私は UML の勉強あまりしていないので普通に文章で書いてます……。

## ドキュメントに起こす
調査した結果を wiki などにまとめます。
項目は概ね以下の通りで良いと思います。

* バッチ名
* 処理概要
* IN/OUT
* 起動方法
* 結果確認方法
* 条件分岐などの詳細なロジック

ドキュメントにまとめたら他のメンバーにレビューをお願いしましょう。
書いたドキュメントを元にソースコードを読んでもらって認識がズレていなければきっと大丈夫。

以上、私が普段やっている謎バッチの解析手順でした。

# まとめ
謎バッチサーバの解析は慣れていないとなかなか骨の折れるタスクです。
この手順で調査すれば万事解決とはいかないと思いますが、誰かの役に立ってくれることを願います。

謎バッチの調査以外でも調査全般における心構えを以下にまとめます。

* 目的を持って調査する
* 巨大なまま戦おうとしない
* わかる部分からたぐり寄せる
* 類似点を探す
* あらゆる可能性を推測する
* 実験してみる
* 調査に詰まったら明日の自分に期待する

特に最後が大事です。
前の日にアホほど詰まった問題も翌日スッキリした脳みそだと意外とすぐに解決できたりします。

私からは以上です。
それでは皆さん素敵な考古学ライフを！