はじめに
以前書いた記事で100万件のレコードを用意したことがあったのですが、せっかくなのでその方法を紹介します。
ついでにちょっとした検証もしたのでそれも紹介します
結論はよ
- 大量データの作成は
activerecord_import
を使おう - 処理の時間を測ってみたらかなりの差が出たよ
中級者以上のかたにとっては当たり前という内容かもなので、「あ、そういう内容ね」とここで想像できた方はこのタイミングでブラウザバックしてもらって構いません笑
やり方
Gem
先に使うgemを紹介します
gem 'activerecord-import'
gem 'seed-fu'
activerecord-import
というgemは大量データを作成する際に使われることが多く、簡単に説明すると作成したい大量のレコードを元に1つのSQL文を作成して、それを実行してくれるものです
詳しい使い方などはここでは解説しませんが、興味のある方は調べてみると良いでしょう。
一応githubのリンクを載せておきます
seed-fu
というgemはseedの実行に使うもので、本記事の本質とは直接関係ないですが、あとで出てくるseed実行のコマンドに必要なので載せています
seedファイル
seedファイルは以下のものを準備します
(当たり前ですが事前にpostsテーブルとpostモデルの作成をお忘れなく)
1000.times do |batch_idx|
posts = []
1000.times do |each_idx|
posts << Post.new(title: "#{batch_idx + 1}_#{each_idx + 1}_test", content: 'test')
end
Post.import! posts
end
100万件のレコードを準備するのに1000件の一括インサート処理を1000回実行するイメージです。
メモリが許せば1万件の一括インサート処理を100回でも、100万件のインサート処理を1回でも良いです。
(インサート処理は少ない方が高速ですが、そのためのインサート文を作るのためのメモリが足りないと実行できないので、バランスを見ながらとなります。1000件ぐらいならメモリが枯渇することはほぼないと思われますし、seedならそこまで高速処理が求められないと思うので今回はこのようにしてます。)
ここまでできたら下記のコマンドを実行します
RAILS_ENV=development bundle exec rails db:seed_fu FILTER=post FIXTURE_PATH=./db/fixtures/large_data_test
これで100万件のレコードが用意できます
計測(おまけ)
100万件となると、100万timesを使う場合と比べて実行時間が大きく違うと思ったので計測してみました
def times_seed
1_000_000.times do |i|
Post.seed do |s|
s.title = "title_#{i + 1}"
s.content = 'sample'
end
end
end
def bulk_import
1000.times do |batch_idx|
posts = []
1000.times do |each_idx|
posts << Post.new(title: "#{batch_idx + 1}_#{each_idx + 1}_test", content: 'test')
end
Post.import! posts
end
end
Benchmark.bm 10 do |r|
r.report 'use times seed' do
times_seed
end
r.report 'use activerecord import' do
bulk_import
end
end
これを実際に試すと次のような結果になりました。
$ RAILS_ENV=development bundle exec rails db:seed_fu FILTER=post FIXTURE_PATH=./db/fixtures/large_data_test [~/projects/rails7_sample]!+[main]
== Filtering seed files against regexp: /post/
== Seed from ./db/fixtures/large_data_test/post.rb.rb
user system total real
use times seed - Post {:title=>"title_1", :content=>"sample"}
- Post {:title=>"title_2", :content=>"sample"}
- Post {:title=>"title_3", :content=>"sample"}
- Post {:title=>"title_4", :content=>"sample"}
...
...
...
- Post {:title=>"title_999998", :content=>"sample"}
- Post {:title=>"title_999999", :content=>"sample"}
- Post {:title=>"title_1000000", :content=>"sample"}
1043.111819 75.961859 1119.073678 (8733.553886)
use activerecord import 64.518842 0.569162 65.088004 ( 87.468840)
実際には100万timesの処理をすると100万行のログが出力されるので、かなり見づらい出力結果になってしまいます(そもそも表示行数を100万行以上にしてないと最初から最後まで見れません笑)
少し分かりやすいように表にしてみます
方法 | 処理時間 /s |
---|---|
times | 1119.073678 |
activerecord-import | 65.088004 |
だいぶ実行時間に差が出ましたね〜
今回使ったPCはM1 Proでしたが、times使うと20分近くかかりました。
スペック低いPCでこの実験するとCPUが熱々になる可能性もあるのでもし試したい場合は件数少なめから様子見してやるようにしてください笑
大量データを用意する時はactiverecord_import
を使うことも検討してみてはいかがでしょう?(複雑な検索のクエリをテストしたい時など、もしかしたら活躍の場面があるかもしれません)