Ruby on rails Sidekiq,线程耗尽-Rails离开并没有获得连接池线程

Ruby on rails Sidekiq,线程耗尽-Rails离开并没有获得连接池线程,ruby-on-rails,ruby,sidekiq,Ruby On Rails,Ruby,Sidekiq,我们的生产系统运行在以下配置中: Ruby 2.5.1 Rails 5.2.2 Sidekiq 5.2.5 Sidekiq-cron 1.1.0 Redis 4.1.0 它有3个队列(默认/高/中等优先级),每个队列有4个线程。我们最近添加了一个新的sidekiq cron作业,该作业在高队列中每30分钟运行一次,几天后系统就会陷入僵局,无法为连接池生成更多线程。我们已经追踪到了“高”队列和这个新作业,上次它挂起这个“高”队列时,它有1900个线程,几乎所有的线程看起来都是“连接池”。队列进程

我们的生产系统运行在以下配置中:

Ruby 2.5.1
Rails 5.2.2
Sidekiq 5.2.5
Sidekiq-cron 1.1.0
Redis 4.1.0
它有3个队列(默认/高/中等优先级),每个队列有4个线程。我们最近添加了一个新的sidekiq cron作业,该作业在高队列中每30分钟运行一次,几天后系统就会陷入僵局,无法为连接池生成更多线程。我们已经追踪到了“高”队列和这个新作业,上次它挂起这个“高”队列时,它有1900个线程,几乎所有的线程看起来都是“连接池”。队列进程中出现了一个
kill-9
,我们的主管重新启动了它,5-7天内一切都很好,然后又停止了

这个新作业在远程数据库中创建了许多新列表,我们有本地记录的本地ActiveRecord模型和超类RemoteList模型。我们使用
远程模型。建立连接…
。事务打开,写入,写入…,关闭事务,关闭连接。我们与大量的远程数据库进行了交谈,因此该模型对我们来说非常适用

这位新员工反复向使用了3年多但未被锁定的列表发布者发出呼吁。每次通过旧的列表发布器写入远程数据库时,我们都可以看到添加了一个新的连接池进程

我试过:

  • 手动从池中获取连接并返回,对于ActiveRecord本身和我们的超类,一个,两个,无
  • 将块包装在
    ActiveRecord::Base.connection\u pool.with\u connection
  • 以上两种方法都没有任何影响,线程数随着我们拆分的每个文件而不断增加,直到我们达到死锁,不再有线程。收割似乎什么也没做。这个worker唯一不同的地方是它调用了一个“C”程序来执行一些文件拆分,速度比ruby快得多,但是我可以看到生成的shell已经关闭并完成了,但是我们仍然得到了这些“连接池”线程

    任何人都有好主意

    谢谢 凯特

    列表发布

    CoreDBListModel.semaphore.synchronize do
        begin
    
          .... setup removed....
          CoreDBListModel.establish_connection(@config['database'])
    
          CoreDBListModel.transaction do
            core = CoreDBListModel.where(:description => list.list_id).first
            core.pending = true
            core.name = list.name
            core.tags = category.name
            core.pcount = list.count
            core.active = list.deleted ? 2:0
            core.save
    
            ... make list insert data.....
            mass_insert = "INSERT INTO #{mapping['table']} (data_id, data, fulldata) VALUES #{inserts.join(", ")}"
            CoreDBListModel.connection.execute(mass_insert)
    
          # Mark as completed
            core.pending = false
            core.save
    
          end
    
        rescue => e
          @code = 500
          @message = "Failed - #{e.message}, #{e.backtrace[0]}"
          Rails.logger.error("CoreDBList() - Publishing failed - #{list.list_id}")
          Rails.logger.error("CoreDBList() - Publishing failed - #{e.message}")
          Rails.logger.error("CoreDBList() - Publishing failed - #{e.backtrace.first(10).join("\n")}")
        ensure
          begin
            # Close our DB connection
            CoreDBListModel.connection.close
          rescue
          end
        end
      end
    
    我们在下面添加了一个更详细的答案,但基本上问题看起来是Rails在5.1.6和5.2.1之间。如果我们回到5.1.6,问题就会消失


    首先确定线程泄漏的速率-它是在每次任务运行时发生还是在一段时间内偶尔发生?如果是前者-您很幸运,可以通过记录
    ObjectSpace.each_object(Thread.count)
    (注意,这是一个繁重的函数,可能不适合生产中的高负载)在代码中的多个点尝试调试,以检测线程泄漏的位置


    可疑的是
    Open3.capture3
    -它启动两个线程来读取进程的stdin/stdout,另外一个线程用于读取进程退出状态,如果您在C代码中不使用单独的stderr,我建议切换到
    capture2
    ,并查看线程是否泄漏速度慢了约1/3倍。我记得popen3和child进程出现问题,没有使用stderr或打开/关闭std流或不读取stdin的组合,现在无法回忆详细信息。

    不是答案,而是问题的更新,我们的一台服务器今天早上再次宕机,其中一个sidekiq任务有31000多个线程

    看起来这是Rails ActiveRecord的一个问题,我们可以通过一些测试代码成功地将休眠线程留在后面。正是“CoreDBListModel.connection.close”使连接池处于休眠状态,但由于其状态,连接池从未收获

    我们的问题似乎始于从Rails 5.1.5迁移到5.2.1时,如果我们回滚到该版本,问题就会消失,Rails 6中似乎也存在类似的问题,因为其他人也提出了类似的问题,名称略有不同,但问题相同:


    我唯一的建议是,调用
    CoreDBListModel.connection.close的begin、rescue和end块可以很容易地掩盖一些问题,从而正确地关闭。我会考虑至少在那里做一些日志记录,看看是否有问题,甚至只是在救援中没有包装它(但是,这是PROD,也许你不想要)我们在Rails 5.2.4和Ruby 2.61.上有同样的问题。尝试升级到rails 6.1,并尝试降级我们的应用程序以解决这一噩梦。rails 4上的相同代码工作得完美无缺。我们还试过puma、thin和unicorn,它们都有相同的问题,所以这是rails的问题,而不是服务器的问题。几乎每个用户请求都会生成一个永不消亡的新线程。我们还使用了多租户的方法。我们从代码中删除了Open3.capture3作为测试,它仍然抛出线程,似乎没有关联。谢谢你的关注。
    
    CoreDBListModel.semaphore.synchronize do
        begin
    
          .... setup removed....
          CoreDBListModel.establish_connection(@config['database'])
    
          CoreDBListModel.transaction do
            core = CoreDBListModel.where(:description => list.list_id).first
            core.pending = true
            core.name = list.name
            core.tags = category.name
            core.pcount = list.count
            core.active = list.deleted ? 2:0
            core.save
    
            ... make list insert data.....
            mass_insert = "INSERT INTO #{mapping['table']} (data_id, data, fulldata) VALUES #{inserts.join(", ")}"
            CoreDBListModel.connection.execute(mass_insert)
    
          # Mark as completed
            core.pending = false
            core.save
    
          end
    
        rescue => e
          @code = 500
          @message = "Failed - #{e.message}, #{e.backtrace[0]}"
          Rails.logger.error("CoreDBList() - Publishing failed - #{list.list_id}")
          Rails.logger.error("CoreDBList() - Publishing failed - #{e.message}")
          Rails.logger.error("CoreDBList() - Publishing failed - #{e.backtrace.first(10).join("\n")}")
        ensure
          begin
            # Close our DB connection
            CoreDBListModel.connection.close
          rescue
          end
        end
      end