大量データを処理したら途中で動かなくなってしまたんだ・・・
どうしよう・・・
メモリが足りなくなってしまったんだね・・・
大量のデータを扱うときは注意が必要だからその方法を解説するね!
大量のデータを扱うには?
Railsでマイグレーション処理やCSV処理など、DBから大量のデータを取得し、それを加工したいことがあると思います。
データが大量にある場合、取得したデータを繰り返し処理すると、動作が遅くなったり、フリーズして動かなくなったりします。
これは一気に大量のデータを取得することでメモリの容量を超えてしまうためです。
メモリの容量内でデータを処理することができれば、このような問題は回避できるので、大量にデータを扱う場合は小分けにしてデータを取得するようにします。
Railsではこのような処理を想定したメソッドが準備されているので使い方を解説します。
メモリの容量を圧迫してしまうダメなケース
繰り返し処理に使われるeach
メソッドやmap
メソッドは一気に全てのデータを展開して処理をするため、データの件数が多くなるほどメモリの容量を圧迫してしまいます。
数件のデータであれば問題ないですが、数万、数十万件のデータを扱うときにはeachメソッドやmapメソッドを使わないように気を付けましょう。
Model.each do |resource|
resource.update!(name: 'ナマケモノ')
end
分割して取得したデータを1件ずつ処理をする
まずは分割したデータを1件ずつ処理する方法を解説するね!
分割して取得したデータを1件ずつ処理するときはfind_each
メソッドを使います。
分割するデフォルトの件数は1000件です。
例えば10000件データがあったら1000件ずつ分割して取得して1件ずつ処理をします。
find_eachの使い方
find_each
オプション | 説明 | デフォルト値 |
---|---|---|
:batch_size | 分割数 | 1000 |
:start | 開始位置 | |
:order | 順序 | :asc |
Model.find_each do |resource|
resource.update!(name: 'ナマケモノ')
end
例えばModelにデータが10000件あったとすると、find_eachで1000件ずつDBから取得してresourceを1件ずつ処理するイメージだよ。
分割数を100件にしたとき
Model.find_each(batch_size: 100) do |resource|
resource.update!(name: 'ナマケモノ')
end
開始位置を指定したとき
Model.find_each(start: 500) do |resource|
resource.update!(name: 'ナマケモノ')
end
こうやって分割して取得できるんだね!
分割して取得したデータそのまま処理をする
find_eachは分割して取得したデータを1件ずつ処理したけど、
取得したデータをそのまま処理するメソッドがあるよ。
分割して取得したデータをそのまま処理するメソッドとしてfind_in_batches
メソッドとin_batches
メソッドの2つがあります。
2つの違いはfind_in_batchesメソッドはブロックに配列を渡すのに対して、in_batchesメソッドはブロックにActiveRecord::Relation
を渡すことです。
in_batchesメソッドはRails5で追加されたメソッドです。
例えば10000件データがあったら1000件ずつ分割して取得して1000件を処理をします。
find_in_batchesの使い方
find_in_batches
オプション | 説明 | デフォルト値 |
---|---|---|
:of | 分割数 | 1000 |
:load | リレーションをロードするか | false |
:start | 開始位置 | |
:order | 順序 | :asc |
find_in_batchesメソッドは分割したデータを配列で受けとります。
処理する順序は指定できないので順番に処理したい場合はfind_eachを使いましょう。
Model.find_in_batches do |resources|
resources.update_all(name: 'ナマケモノ')
end
例えばModelにデータが10000件あったとすると、find_in_batchesで1000件ずつDBから取得してresourcesを1000件配列で受け取り処理するイメージだよ。
in_batchesの使い方
in_batches
オプション | 説明 | デフォルト値 |
---|---|---|
:of | 分割数 | 1000 |
:load | リレーションをロードするか | false |
:start | 開始位置 | |
:order | 順序 | :asc |
in_batchesメソッドは分割したデータをActiveRecord::Relation
で受けとります。
Model.in_batches do |resources|
resources.update_all(name: 'ナマケモノ')
end
これも例えばModelにデータが10000件あったとすると、find_in_batchesで1000件ずつDBから取得してresourcesを1000件配列で受け取り処理するイメージだよ。
使い方の注意点
これらのメソッドを使うときに気を付けてほしいことがあるよ!
find_eachメソッドや、find_in_batchesメソッド、in_batchesメソッドはモデルクラスに対して使いましょう。
データを変数などに一旦入れて、その変数に対してこれらのメソッドを使っても意味がありません。
変数など別の領域にデータを入れておくこと自体がメモリを圧迫する原因となるからです。
DBから少しずつ分割して取得するためにはモデルクラスに対して直接メソッドを使う必要があります。
resources = Model.all
resources.find_each do |resource|
resource.update!(name: 'ナマケモノ')
end
Model.find_each do |resource|
resource.update!(name: 'ナマケモノ')
end
変数に対してfind_eachは動くから気が付かないことがあるけど、
注意してね!
気を付けるね!
まとめ
今回は大量のデータを処理する方法を解説したよ!
- eachメソッドやmapメソッドは大量にデータを処理するときには使わない
- 分割して取得したデータを1件ずつ処理をするときはfind_eachメソッドを使う
- 分割して取得したデータそのまま処理をするときはfind_in_batchesメソッドとin_batchesメソッドを使う
- 変数に対して使っても意味がない
大量のデータを扱うときには気を付けて処理するようにだね!