46957


seed_fuを使ったseed投入をマルチスレッドで実行しようとして以下のようなコードを書きました。

[code]

    # 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)
    
[/code]

これを実行すると `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

* * *

以下のようにしました。

[code]

    # 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)
    
[/code]


CC BY-SA 4.0
2018-07-24T14:50:37.083
46918
2018-07-26T09:57:33.440
2018-07-26T09:57:33.440
28630
28630
question
ruby-on-rails ruby mysql rails-activerecord
railsのdb:seedをマルチスレッドで実行しようとするとActiveRecord::ConnectionTimeoutErrorが発生する


ActiveRecord の connection_pool 自体に、排他制御がかかっています。プログラム全体で見たときに、対象 DB
への同時接続の排他制御に責任を持っているのは、なので、 connection_pool だと思います。

例えば、導入してた gem が何か勝手に connection を取得していた、とかはありうる話かな、と思っています。

だとすると、Thread を以下のように実行して、排他制御は connection_pool に任せる、というのはいかがでしょうか?

[code]

      Thread.new do
        depend_thraeds.each(&:join)
        loop do
          begin
            ActiveRecord::Base.connection_pool.with_connection { SeedFu.seed(SEED_PATH, seed_file_name) }
            break
          rescue ActiveRecord::ConnectionTimeoutError
          end
        end
      end
    
[/code]


CC BY-SA 4.0
2018-07-25T10:58:58.263
46957
2018-07-25T10:58:58.263
754
46918
answer