Rails の db:seed で parallel を使って大量データの投入を高速化する

2021-8-18

db:seed、便利ですよね。 Faker と組み合わせれば以下のような感じで Article データを作成できます。

seeds.rb
Faker::Config.locale = "ja"
100.times do |_i|
  Article.create!(
    title: Faker::Coffee.blend_name,
    body: Faker::Markdown.sandwich,
    published: true
  )
end

ただ、負荷テスト目的なので大量のフェイクデータを作成したい場合、ループ処理だと時間がかかります。そこでスレッドを作成して並列実行できる parallel を使います。子プロセスを立ち上げる方式だと環境変数の共有やらが大変そうなので、大抵の場合はマルチスレッドモードでよさそうな気がしますね。

sample.rb
## maybe helps: explicitly use connection pool
Parallel.each(User.all, in_threads: 8) do |user|
  ActiveRecord::Base.connection_pool.with_connection do
    user.update_attribute(:some_attribute, some_value)
  end
end

上の例のようにコネクションプールは使い回すようにします。そうしないとスレッドごとにコネクションプールが貼られてしまい、オーバーするとスレッドが中断され、最初の数件しか処理されない、という結果になります。では seeds に適用してみます。

seeds.rb
Faker::Config.locale = "ja"

Parallel.each(0...10000, in_threads: 5) do |item|
  ActiveRecord::Base.connection_pool.with_connection do
    Article.create!(
        title: Faker::Coffee.blend_name,
        body: Faker::Markdown.sandwich,
        published: true
    )
  end
end

これで並列にフェイクデータが作成できます。並列化なしだとだいたい千件投入するのに5分くらいかかってしまっていたのですが(1万件いれようとすると途中でDBコネクションが閉じられる)、並列化を行うと1万件投入に2,3分くらいで完了します。圧倒的速さ。

テストデータで大量のフェイクを作りたい場合に、参考になれば幸いです。