dreamin' blog

別に問題じゃなさそうなことに問題って名前をつけたのは公式で用意してくれているバッチインターフェースが二つあって、いつも迷っちゃうからだ。

$ rails foo:bar:baz

のようにコマンドを作れるのがrake taskです。
これは普通にrails db:migraterails g scaffold Fooのようにいろいろなところで開発者が使うからおなじみのコマンドです。

もう一つのインターフェースが、rails runnerと呼ばれるやつで、こちらは引数に自由にruby(しかもrailsの資産をつかった)のコードを書くことができる。

普通にバッチを書くならどっちでも問題ない

基本的にどっちを使っても問題はありません。しかしrails runnerを作るときはインターフェースを自分で考える必要があります。以前言及した記事としては app/batches 配下にバッチフレームワークのようなものを作り、 rails runner FooBatch.run のように書くものでした。

自前のバッチフレームワークのようなものが必要なのはひょっとしたら過去の時代の話かもしれません。例えばkuroko2やjenkins、rundeckのようなcron replacementと呼ばれるツール群を使えば、共通処理で書いていた排他制御やロギングなどをそちらに任せることができます。そしてkuroko2やjenkinsの先ではrailsの入ったdockerを実行させるようなことも簡単にできます。
また、dockerの話を続けるとk8sのcronojobを使えばGKEやAKSのサポートの上で、kuroko2で得ていた問題を吸収することができます。

rails runnerrake task のどちらを使おうとも、結局バッチを実行するツールが実行するインターフェースはdocker runのようなものだったりするわけです。

そもそもバッチっていつ書く?というもう一つの問題

そもそもrailsをつかっているということは、おそらくはWebサービスのバッチを動かすということだと思います。
基本的にバッチ処理にしなくてもバックグラウンドジョブを利用するようにした方が、エンドユーザーにとっては処理完了のタイムラグも短くsidekiqやrescueのようなワーカーを使えばリトライも自動的に行ってくれるというメリットがあります。

それでもバッチ処理はある時点で行う集計を伴う処理には必要なものであると言えます。

思考停止でバッチにやらせない判断をする

僕は新規のサービスに携わることが多いですが、ほとんどバッチを使う機会がなくなってきている印象にあります。集計は基本的にredashのようなツールを用いるため、railsが関与することは減らすことができています。

一方ジョブキューの処理はちょっとしたデータの更新や重い処理を行うのにも非常によく使うようになってきています。
例えばトランザクションに影響されないAPIコールなども可能な限りバックグラウンドジョブで行うようにしてretryの判断をジョブキューで行わせるようにしています。

これは月次や日次のバッチに書き直そうとなるときもあるのですが、一旦ジョブキューで行わせるように検討してから、時間がかかりすぎるからバッチにしましょうと後で移行することも増えてきました。

そこで浮上する第三のバッチインターフェース

これはバッチように作られたインターフェースではないのですが、activejobでバッチ処理をキックする運用をやって意外とうまくいくじゃんという成功体験がありましたのでシェアします。

namecpace :es do
  task index_rebuild: enviroment do
    ElasticSearch::IndexRebuildJob.perform_now
  end
end
class Elasticsearch::IndexRebuildJob < BatchJob
  def perform(foo)
  end
end

こんな感じでindex rebuildを最初は管理画面から実行して行っていたのですが、時間がかかるようになりワーカーサーバーとは別のコンテナで実行したいという要望がでてきました。
そこでrake taskから同期的にActiveJobを呼び出すという荒技をやってしまうと意外とバッチインターフェースとしてしっくりきました。

上記では BatchJob を継承していますが、rake taskでは難しかった処理の共通化をフックメソッド before_perform などを使って制御することも可能になります。

まとめ

ここ最近バッチ処理どうしようかなぁという人をちょいちょい見かけたので、だらだらと書いてみました。