開発備忘録 2018/10/16

[Rails] バッチ処理のパフォーマンスについて考える

バッチで大量のデータに対して何かしらの処理を行うとしたときに、こんな感じで実装してみる。

User.all.each do |user|
  # 処理
end

ふむふむ、なるほど。

これが例えば User.all で取得した件数が 100 件とかだったらまだ大丈夫。
しかしサービスが成長して会員数が 1 億人を突破しましたと。
そうするとこのバッチ処理は途端に破綻する。

何故かというと User.all で取得した ActiveRecordRelationeach で回そうとしたときに 1 億件の ActiveRecord をメモリ上に展開しようとします。
ActiveRecordインスタンスの生成はかなり重いので、1 億件ともなるとメモリが足りなくなり最悪バッチが止まってしまいます。

これを回避するためには、each ではなく find_each を使いましょう。

User.find_each do |user|
  # 処理
end

これにより (デフォルトでは) 100 件毎に user を取得し処理を行うようになります。

Rails で例外クラスを定義するときは Exception ではなく StandardError を継承するようにしよう

まず前提として、Exception は全ての例外クラスの祖先にあたるクラスで、StandardError はその子にあたる。
ここで Exception を継承した MyError クラスを定義し、適当に例外を補足するような実装をしてみる。

class MyError < Exception; end

begin
  raise MyError, 'my error'
rescue => error
  p error
end

しかしこの場合は例外は補足されない。
Ruby では rescue の後にクラスを省略した場合、StandardError の例外を補足するようになるが、MyErrorException を継承しているため、この場合は例外が補足されない。

というわけで、今度は rescue で Exception も補足できるようにしてみる。

begin
  raise MyError, 'my error'
rescue Exception => error
  p error
end

これで MyError を補足できるになる。が、これは悪手だ。
Exception を補足できるようにしてしまうと、OS レベルの例外も補足してしまうため、意図していない場面でアプリケーションが落ちてしまう可能性がある。

このような理由から Rails で例外クラスを定義する場合は StandardError を継承する方が良い。