圧倒亭グランパのブログ

30年後の自分にもわかるように書くブログ

人に優しい定期バッチ処理を書く

この記事は VOYAGE GROUP Advent Canlendar 2015 の17日目の記事です。

こんにちは。 @at_grandpa です。

Slackで将棋を指したあの日 から、もう1年が経つのですか。

早いですねぇ。

ちなみに at_grandma は、 Heroku様の神の裁き によって無期限活動停止中です。

 

さて、最近の自分はというと、定期バッチ処理を書くことが多いです。

定期バッチ処理

それは、サービスを運用する上で、少なからずお付き合いすることになるもの。

今回は、「人に優しい定期バッチ処理を書く」ために自分が普段心掛けていることを書こうと思います。

 

定期バッチ処理とは

言葉の通り、定期的にバッチ処理を行うことです。

メジャーなものとしては、crontab を用いての定期処理などが挙げられます。

用途としては、例えば、

  • 日次集計バッチ
  • 月次集計バッチ
  • 定期お掃除バッチ
  • 1時間毎の最適化バッチ
  • 監視バッチ

などなど、挙げ始めたらキリがありません。用途は多岐に渡ります。

定期バッチ処理を書いているといろいろと考える点があります。今回の内容は以下の6つ。

  1. コケた箇所を把握できるようにログを吐く
  2. 途中から再開できるようにする
  3. 冪等性(べきとうせい)を担保する
  4. リリース後の挙動を多方面からチェックする
  5. DRY_RUN mode
  6. README.mdを更新する

 

1.コケた箇所を把握できるようにログを吐く

バッチ処理は、どんな理由であれ必ずコケます。そういうものなのです。潔く認めましょう。

そして、そのコケた箇所を把握できるようにしましょう。でないと、リカバリできません。

理経過をログに吐き出すと、経過を後から追うことができます。

I, [2015-12-17T01:42:07.806195 #18175]  INFO -- : 最適化処理開始
W, [2015-12-17T01:42:15.152063 #18481]  WARN -- : user_nameが空です [USER_ID:34525]
W, [2015-12-17T01:42:17.001054 #18481]  WARN -- : user_nameが空です [USER_ID:66452]
I, [2015-12-17T01:42:18.951053 #18481]  INFO -- : 更新件数 1034 件
I, [2015-12-17T01:42:23.398336 #18856]  INFO -- : 削除件数 556 件
I, [2015-12-17T01:42:43.968574 #18956]  INFO -- : ファイル [/var/tmp/result_20151217.tsv] への書き込み完了

件数や書き込み先などを出力しておくと、件数が異常に変わったのはいつからか把握できたり、自分以外の人がログを見た時に有力な情報を与えることができます。

ですが、有力なログ吐き出しはなかなか難しいものです。運用過程が変わってくると欲しい情報も変わってきますし。

コツは以下のようなものかなと思います。

  • 各処理のポイントでログを出力する
  • コケた時のリカバリを想像して、必要な情報をログに出力する
  • 新たな情報が必要になったら追加するが、ログの出力量を確認する(情報量が多すぎないか、ディスクは大丈夫か)

昔、私は「ありったけのログを吐いておけば、欠損情報の原因がわかる!」と思い、細かにログを吐き出すプログラムを書いていました。

無論、そのログは、情報量が多すぎて誰にも見てもらえず、さらにはディスクを圧迫し、OPSチームに迷惑をかける事態となってしまいました。

適切なログ吐き出しで、自分にも優しく、周りにも優しいバッチを書きましょう。

 

2.途中から再開できるようにする

コケたバッチはコケた地点から再実行できるようにしましょう。

方法としては、「各処理単位で結果をファイルに吐き出す」のがオススメです。

例としてはこんな感じです。(Makefile)

run: target calc update

target:
    php target.php --date=$(DATE) > $(TARGET_FILE)

calc:
    php calc.php --date=$(DATE) --target=$(TARGET_FILE) > $(CALC_RESULT)

update:
    php update.php --date=$(DATE) --result=$(CALC_RESULT)

メリットとしては以下の点があります。

  • コケる手前の処理までは成功しているので、その結果ファイルさえあれば途中から再実行できる
  • テスト用ファイルを用意すれば、全体の処理を簡単にテストできる
  • 出力ファイルのフォーマットさえ決めてしまえば、処理は何の言語で書いても良い

しかし、これも銀の弾丸ではなくて、

  • ディスクを圧迫してしまう可能性がある
  • 速度が求められる場合、ファイルIOはかなりのロスとなる

などのケースがあります。この辺りは都度考える必要があるでしょう。

再実行するのは自分だけではないのです。周りにも優しい再実行機構を。

 

3.冪等性(べきとうせい)を担保する

これはバッチ処理では重要な要素です。

冪等性 = 「引数が同じであれば、バッチを何度叩いても結果が同じになる」 です。

以下のようなものはNGです。

  • バッチを何度も叩くと、DBのレコード件数が増えていく(または減っていく)
  • バッチを何度も叩くと、パラメータの値が徐々に変わっていく

冪等性のメリットは、

  • 自動実行でコケても、手動実行でリカバリできる
  • 何回叩いてもOKという安心感
  • 他のバッチへの依存性が少なくなる

です。これで安心してリトライできますね。

例えば、「1日分の集計データ1000件をDBに保存する」というバッチの場合、その日付のレコードを一旦 delete してから insert します。

こうすることで、そのバッチを何回叩いても、日付さえ同じであれば結果は変わりません。

冪等性が維持されていなければ、叩くのすら怖いバッチになってしまうでしょう。

 

4.リリース後の挙動を多方面からチェックする

リリース後の挙動をチェックするのは当たり前ですが、無事バッチが終わったからといって安心してはいけません。

下記項目を、定期間隔の1周期分は必ず見ましょう。

バッチの実行結果が妥当か

これはバッチの直接的な挙動確認です。

妥当な件数が更新されているか、パラメータは不可解な値になっていないか。

実行後にすぐにチェックしましょう。

サーバ監視項目

CPU, メモリ, トラフィック, ディスク容量, etc...

バッチ処理は大抵重い処理になりがちです。

その分、サーバーにかかる負荷も無視できません。

また、定期バッチが並行で複数走っている場合などは、他のバッチに影響を与えてしまうかもしれません。

できれば負荷チェックは開発環境で事前に行うべきですが、基本、本番環境のサーバーと全く同じパフォーマンスではないので、本番での様子見も必ず行いましょう。

サービスのKPIの挙動

これは忘れがちですが、非常に重要です

バッチが動いたということは、何らかの状態が変わったわけで、その影響はサービスに直接関わってきます。

ユーザーのアクセス数は変わっていないか、配信量に変化はないか、売上が極端に変わっていないか、etc...

これらの項目は大丈夫ですか?

大事なことは、バッチが正常に終わったからといって サービスが正常である保証はない ということです。

「バッチは何のエラーも吐かずに終わったが、パラメータの値が全て0になっていた...」

ということも十分起こり得ます。

バッチを書いて動作確認して終わり、、、ではないのです。サービスのためにバッチを書いているわけで、サービスに悪影響がないことを必ず確認しましょう。

ここまでの動作チェックを行えば、ディレクターさんにも優しいバッチ処理になります。

 

5.DRY_RUN mode

間違ったバッチを実行してしまった!

ああああ!DBにupdateが走ってしまっている!!!!

Ctrl+CCtrl+CCtrl+C

...

安心してください。それ、DRY_RUN mode ですよ。

 

何も考えずに引数を指定してバッチを手動実行。

$ php my_batch.php --date=2015/12/17

しかしこれは DRY_RUN mode であり、計算処理は行いますが、DBへの更新は一切行わないようにするのです。

実際にDBに書き込みを行うときは、

$ php my_batch.php --date=2015/12/17 --run

と、明示的に --run を引数に指定しなければならないようにします。

DBにアクセスする前に、必ず一度立ち止まることができます。

人に優しいバッチですね。

 

6.README.mdを更新する

修正したら README.md を更新しましょう。

そこのあなた、忘れていませんか?

あなたの書いたコードとREADME.mdの内容が違っていたら、「あなたの担当した箇所のREADME.mdは信用できない」となってしまいます。

そうすると、README.md は存在しないも同然になってしまいます。

あなたのコードはみんなが触るのです。

 

まとめ

いかがでしたでしょうか。人によっては当たり前かもしれませんが、自分にとっては色々と気付く点があったのでまとめました。

まぁ、ケースバイケースなことも多いですが、一般的に考えるべきことなのかなと思います。

これらの他にも、

  • リカバリコマンドを用意するのは正しいのか?
  • オレオレバッチではなく、チームの慣例に従う
  • 開発環境で、出来るだけ本番に近い状態でチェックする
  • 一夜漬けdeploy

などがありますが、今回はここまで。

明日は @_yukinoi さんです。

なんの話なんだろーなー。