【问题标题】:Rails + Sneakers: could not obtain a connection from the poolRails + Sneakers:无法从池中获取连接
【发布时间】:2019-03-18 20:38:52
【问题描述】:

我们将 Sneakers gem 用于大型应用程序的生产。有时负载可能非常巨大,以至于一个特定的队列可能包含超过 250_000 条消息。在这种情况下,例外

ActiveRecord::ConnectionTimeoutError: 
could not obtain a connection from the pool within 5.000 seconds (waited 5.000 seconds); all pooled connections were in use

定期发生。

对于数据库,我们使用基于 PostgreSQL 9.6 的 Amazon RDS。 max_connectionsPostgreSQL 配置值为 3296。

我们的database.yml 文件:

production:
  adapter: postgresql
  encoding: utf8
  pool: 40
  database: <%= ENV['RDS_DB_NAME'] %>
  username: <%= ENV['RDS_USERNAME'] %>
  password: <%= ENV['RDS_PASSWORD'] %>
  host: <%= ENV['RDS_HOSTNAME'] %>
  port: <%= ENV['RDS_PORT'] %>

我想我们可以增加一个pool 值,但我找不到有关如何计算最大可能值的信息,所以它不会破坏任何东西。

此外,使用 Sneakers gem 进行后台处理的应用程序副本单独存在(但使用相同的数据库)并且可以单独配置。但现在它具有相同的database.yml 配置。运动鞋 gem 配置文件:

production:
  heartbeat: 2000
  timeout_job_after: 35
  exchange_type: :fanout
  threads: 4
  prefetch: 4
  durable: true
  ack: true
  daemonize: true
  retry_max_times: 5
  retry_timeout: 2000
  workers: 4

我们在基本运行时应用程序中的连接池没有问题,但ActiveRecord::ConnectionTimeoutError 经常出现在工作人员中,这是一个非常大的问题。

所以,请帮我重新配置databese.yml文件:

  1. 如何正确计算pool 选项的最大可能值 如果数据库max_connections 的值为3296?
  2. 如何正确计算pool 选项的最大可能值 当使用带有上述配置的 Sneakers gem 时?
  3. 或者,如果我的配置不错,如何避免在工作人员中使用ActiveRecord::ConnectionTimeoutError

提前致谢。

【问题讨论】:

  • 您应该提供您正在使用的 Rails 版本。如果您使用的是 Rails 5+,您可以尝试将您的应用程序代码包装在 Rails.application.executor { } 中(中间件或只是在您的工作中硬编码)。我没有使用 Sneakers 的经验,但我怀疑它不会自行释放与池的连接,应该可以解决它。有关该机制的更多详细信息:guides.rubyonrails.org/threading_and_code_execution.html。请告诉我进展如何。
  • @SvetlinSimonyan 我正在ActiveRecord::Base.connection_pool.with_connection 块内执行工作人员的代码。如果我没记错的话,Rails.application.executor.wrap 在后台使用它。

标签: ruby-on-rails ruby postgresql rabbitmq sneakers


【解决方案1】:

在等待答案的同时,我一直在寻找解决方案。

而且,我想,我最根本的问题是连接池大小。

在 Sneakers gem 问题跟踪器上,我找到了一个 comment,其中包含计算满载时所需连接数的公式。我稍微更改了评论中的代码,所以现在它会根据每个工人的个人设置进行计算:

before_fork = -> {
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.connection_pool.disconnect!
    Sneakers.logger.info('Disconnected from ActiveRecord!')
  end
}

after_fork = -> {
  def count_pool_size
    workers              = ::Sneakers::Worker::Classes
    default_threads_size = ::Sneakers.const_get(:CONFIG)[:threads]
    base_pool_size       = 3 + workers.size * 3

    if Sneakers.const_get(:CONFIG)[:share_threads]
      base_pool_size + default_threads_size
    else
      base_pool_size + connections_per_worker(workers, default_threads_size)
    end
  end

  def connections_per_worker(classes, default)
    classes.inject(0) do |sum, worker_class|
      sum + (worker_class.queue_opts[:threads] || default)
    end
  end

  def reconfig?
    Rails.env.production?
  end

  ActiveSupport.on_load(:active_record) do
    config = Rails.application.config.database_configuration[Rails.env]
    config.merge!('pool' => count_pool_size) if reconfig?

    ActiveRecord::Base.establish_connection(config)
    Sneakers.logger.info("Connected to ActiveRecord! Config: #{config}")
  end
}

总结:对于所有工作人员,我需要在最大负载下限制近 600 个连接。但我只有 40 个。现在,我将使用上面的代码。希望这会有所帮助。

【讨论】:

    【解决方案2】:

    来自here

    work() 之后 Active Record 似乎没有释放连接 方法完成。如果您使用 Active Record 将块传递给 ActiveRecord::Base.connection_pool.with_connection,它将释放 完美连接。

    def work(msg)
      id = JSON.parse(msg)['id']
      ActiveRecord::Base.connection_pool.with_connection do
        user = User.find(id)
        user.name="Homer Simpson"
        user.save
      end
      ack!
    end
    

    【讨论】:

    猜你喜欢
    • 2018-03-18
    • 1970-01-01
    • 2021-11-07
    • 2017-10-07
    • 2014-01-09
    • 1970-01-01
    • 2017-09-15
    • 2018-11-25
    • 2011-05-28
    相关资源
    最近更新 更多