安全なバッチ処理の作り方

このまえ登り坂の途中でロードバイクのタイヤが破裂しました。ながたです。 今回はバッチ処理について書いてみようと思います。

バッチ処理?

Webサービスの処理開始条件は、大まかに次の2つに分けることができます。

  • ユーザーのアクションに起因するもの
  • ユーザーのアクションに起因しないもの

このうち後者の処理をバッチ処理が担当することになります。

バッチ処理の担当分はさらに、

  • 特定の条件(時間やサービスの状態)で実行するもの
  • 手動で実行するもの

の2つに分けられます。 今回はこの「手動で実行するもの」について書きたいと思います。

バッチを手動実行するのはどんなとき?

バッチ処理を手動で実行するのは、十中八九イレギュラーな状況が発生したときです。 ルーチンワークや実行の条件が決まっているものは何らかの方法で自動化できるはずです。

そしてイレギュラーな状況のほとんどは不具合が発生したとき。 つまり 重ねてミスができない状況 です。

ミスができない状況で、できるだけミスの発生を抑えるバッチ処理の (主にヒューマンエラーを防ぐための)ノウハウを紹介していきます。

本番と同じ環境でテストする

大前提です。

「開発環境ではうまくいったんだけど・・・」なんていいわけは通用しません。 必ず 開発環境で本番と同じ環境を再現してテストしましょう。

バッチ実行前にデータのバックアップをとる

処理に間違いがあってもすぐに復元できるよう、 処理の実行で書き換えられる可能性があるデータはバックアップをとっておきましょう。

バッチファイルに処理の本体を書かない

これはテストをしやすくするための手段です。

バッチファイルに直接処理を書くと、テストが非常にやりにくくなります。 よほど単純な処理でない限り、モデルに移して(できれば既存の処理を再利用して) テストしやすい構成にしましょう。

コマンドライン引数はわかりやすく

バッチ処理にコマンドラインから引数を渡すときは、 よりわかりやすい名前付きの引数を使うようにしましょう。

$ perl bat.pl 10 1 "test"

$ perl send_message.pl --target=10 --category=1 --message="test"

デフォルトはdry-run

「うっかり実行しちゃった(テヘ」 なんてことがあっても焦らないように、 何も指定しなければ dry-run(実行はするがデータは書き換えない)になるようにしましょう。

dry-run

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!"

実際に実行

--prod を付けなければ実行されないようにしておく。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod

実行前に確認ダイアログを出す

これもうっかり対策。 実際に処理を行う前に、どのような処理が行われるか表示して、 問題があればそこで中断できるようにしましょう。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod
send message # 処理の内容を表示
target  : 10
category: 1
message : wryyy!!

continue? [y/N] # ここでnとすれば中断できるようにする

実行前に処理の内容を確認できるだけで、安心感が段違いです。

実行に必要な条件を組み込んでおく

状態によっては実行してはいけない処理がある場合は、 その状態の判断をバッチに組み込んでおきましょう。 「実行するときに気をつけていれば大丈夫!」 なんて思っていると後で泣くことになります。

  • 二重実行するとまずい処理
  • 特定の時間に実行するとまずい処理
  • 逆に、特定の時間以外に実行したくない処理

など、条件を洗い出しておきましょう。

ログを残す

バッチ処理でデータを変更したら、その内容をログとして残すようにしましょう。 標準出力をファイルに書き出してもいいですし、 バッチ内でファイルに書き出す仕組みを入れてもいいでしょう。

標準出力をログとして利用する場合は tee を使うといいでしょう。 標準出力・ファイル両方に出力されるので、状況確認しつつログを残すことができます。

$ perl send_message.pl --target=10 --category=1 --message="wryyy!!" --prod | tee send_message.log

ログを残すことには、おもに以下の2つの理由があります。

  • あとで処理の内容が間違っているとわかったときに、データの復元ができるようにする
  • 途中でバッチ処理が停止したときに、途中からやり直せるようにする

もちろん後者は、途中で処理が止まる→途中から再実行 ができるように、 以下のようにつくっておく必要があります。

  • 一部分だけ完了しても問題がない
  • 途中から再実行するためのオプションを付ける、あるいは処理状況を見て自動で中断箇所から再開するようにする
  • 再実行してもすでに完了している処理・再実行時に行われる処理に影響がない

処理が完了している部分としていない部分が混在するとまずい場合は、 すべての処理が完了するまでデータを書き換えないようにしておきましょう。 このあたりは場合によりけりです。

実行状況の表示

バッチ処理の実行状況を出力するようにしておくと、

  • 実際に処理が行われているのか
  • どこまで処理が終わっているのか

がわかって安心できます。

バッチの完了を通知する

バッチ処理の間、常に張り付いているわけにはいかない。 でも処理が終わったらすぐに確認したい・・・。

というときはバッチ完了時にメールやIMで通知するようにしておきましょう。

バッチ処理の終了時刻が予想できるなら、 終了よりも少し前に通知を送るようにするとタイムロスを防げます。

手動バッチに限らず、 何かしらイレギュラーな状態(自動バッチが途中で止まった、データの処理数が想定より少ない、etc) になったら通知するようにしておくと、トラブルに迅速に対応できるようになります。

ネットワーク切断対策

サーバー上で長時間走らせるバッチの場合、 うっかり途中でネットワークが切断したりすると泣きをみることになります。

screentmux 上で実行したり、 nohup を付けて実行するなどの対策をとっておきましょう。

おわり

今回あげたものは、ちょっと手間をかければすぐに実行できるものばかりです。 保険の宣伝に出てきそうな 少しの投資で大きな安心 という言葉がそのまま当てはまります。

以上、batノウハウでした。

カヤックでは自動化が得意な技術者も募集しています!