railsのdb:seedをマルチスレッドで実行しようとするとActiveRecord::ConnectionTimeoutErrorが発生する
seed_fuを使ったseed投入をマルチスレッドで実行しようとして以下のようなコードを書きました。
# frozen_string_literal: true
SEED_PATH = "db/seeds/production"
# 同時に動くthreadの数をconnection_poolの数に制限する
LOCKS = Queue.new
ActiveRecord::Base.connection_pool.size.times { LOCKS.push :lock }
def seed_thread(seed_file_name, *depend_thraeds)
Thread.new do
depend_thraeds.each(&:join)
lock = LOCKS.pop
ActiveRecord::Base.connection_pool.with_connection { SeedFu.seed(SEED_PATH, seed_file_name) }
LOCKS.push lock
end
end
{}.tap do |t|
t[:region] = seed_thread %r{/region.rb}
t[:prefecture] = seed_thread %r{/prefecture.rb}, t[:region]
t[:aaa] = seed_thread %r{/aaa.rb}
t[:bbb] = seed_thread %r{/bbb.rb}
t[:ccc] = seed_thread %r{/ccc.rb}, t[:aaa], t[:bbb]
t[:ddd] = seed_thread %r{/ddd.rb}, t[:aaa], t[:ccc]
t[:eee] = seed_thread %r{/eee.rb}
# 実際にはもっとテーブル数は多い
end.values.each(&:join)
これを実行すると ActiveRecord::ConnectionTimeoutError
が発生することがあります。
発生確率はActiveRecord::Base.connection_poolの数を1にすると100%、5にすると体感で50%くらいです。
実行中のthreadの数 = connection_poolの数のときlockをかけて ActiveRecord::Base.connection_pool.with_connection {}
を実行させないつもりのコードなのですが、なぜ ActiveRecord::ConnectionTimeoutError
が発生してしまうのでしょうか?
ご教授ください。
- ruby: 2.5.1
- rails: 5.2.0
- seed_fu: 2.3.9
以下のようにしました。
# frozen_string_literal: true
SEED_PATH = "db/seeds/production"
THREAD_NUM = 4
LOCKS = Queue.new
THREAD_NUM.times { LOCKS.push :lock }
def seed_thread(seed_file_name, *depend_thraeds)
Thread.new do
depend_thraeds.each(&:join)
lock = LOCKS.pop
begin
ActiveRecord::Base.connection_pool.with_connection { SeedFu.seed(SEED_PATH, seed_file_name) }
rescue ActiveRecord::ConnectionTimeoutError
STDERR.puts "retry #{seed_file_name}"
sleep 1
retry
end
LOCKS.push lock
end
end
{}.tap do |t|
t[:region] = seed_thread %r{/region.rb}
t[:prefecture] = seed_thread %r{/prefecture.rb}, t[:region]
t[:aaa] = seed_thread %r{/aaa.rb}
t[:bbb] = seed_thread %r{/bbb.rb}
t[:ccc] = seed_thread %r{/ccc.rb}, t[:aaa], t[:bbb]
t[:ddd] = seed_thread %r{/ddd.rb}, t[:aaa], t[:ccc]
t[:eee] = seed_thread %r{/eee.rb}
# 実際にはもっとテーブル数は多い
end.values.each(&:join)