优化Sidekiq,Redis,Heroku和Rails

时间:2014-06-25 08:34:18

标签: ruby-on-rails postgresql heroku redis sidekiq

所以我试图通过Heroku Worker实例上的Sidekiq后台作业处理来处理CSV文件。虽然我可以完成这个过程,但我觉得它可以比我目前正在做的更快更有效地完成。这个问题包含两个部分 - 首先是正确设置数据库池,其次是如何优化流程。

应用程序环境:

  • Rails 4应用程序
  • 独角兽
  • Sidekiq
  • Redis-to-go(迷你计划,最多50个连接)
  • CarrierWave S3实施
  • Heroku Postgres(标准Yanari,最多60个连接)
  • 1 Heroku Web dyno
  • 1 Heroku Worker dyno
  • NewRelic monitoring

配置/ unicorn.rb

worker_processes 3
timeout 15
preload_app true

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  if defined?(ActiveRecord::Base)
      config = ActiveRecord::Base.configurations[Rails.env] ||
                  Rails.application.config.database_configuration[Rails.env]
      config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
      config['pool']            =   ENV['DB_POOL'] || 2
      ActiveRecord::Base.establish_connection(config)
    end
end

配置/ sidekiq.yml

---
:concurrency: 5
staging:
  :concurrency: 5
production:
  :concurrency: 35
:queues:
  - [default, 1]
  - [imports, 10]
  - [validators, 10]
  - [send, 5]
  - [clean_up_tasks, 30]
  - [contact_generator, 20]

配置/初始化/ sidekiq.rb

ENV["REDISTOGO_URL"] ||= "redis://localhost:6379"

Sidekiq.configure_server do |config|
  config.redis = { url: ENV["REDISTOGO_URL"] }

  database_url = ENV['DATABASE_URL']
  if database_url
    ENV['DATABASE_URL'] = "#{database_url}?pool=50"
    ActiveRecord::Base.establish_connection
  end

end


Sidekiq.configure_client do |config|
  config.redis = { url: ENV["REDISTOGO_URL"] }
end

数据库连接池按原样解决:

我有3个Web进程(unicorn worker_processes),每个我通过after_fork挂钩(config / unicorn.rb)分配2个ActiveRecord连接,共分配给Web的60个可用的Postgres连接中的6个(最大)连接赛道。在Sidekiq初始化程序中,我通过附加到ENV [' DATABASE_URL']的?pool = 50 param分配50个Postgres连接,如文档中所述(某处)。我将我的Sidekiq并发值保持在35(sidekiq.yml)以确保我保持在50 Redis连接和60 Postgres连接限制之下。这仍然需要更细粒度的调整,但我宁愿对数据处理本身进行排序,然后继续进行调整。

现在,假设上述内容是正确的(如果不是,那就不会让我感到惊讶)我会处理以下情况:

用户通过浏览器上传要处理的CSV文件。此文件可以是50行到1000万行之间的任何位置。该文件通过CarrierWave gem上传到S3。

然后,用户通过UI为导入配置几个设置,其结果是将一个FileImporter作业添加到Sidekiq队列,以开始基于行创建各种模型。

导入工作人员看起来像:

class FileImporter
  include Sidekiq::Worker
  sidekiq_options :queue => :imports

  def perform(import_id)
    import = Import.find_by_id import_id

    CSV.foreach(open(import.csv_data), headers: true) do |row| 
      # import.csv_data is the S3 URL of the file

      # here I do some validation against a prebuilt redis table  
      # to validate the row without making any activerecord calls
      # (business logic validations rather than straight DB ones anyway)        

      unless invalid_record # invalid_record being the product of the previous validations

        # queue another job to actually create the AR models for this row
        ImportValidator.perform_async(import_id, row)

        # increment some redis counters
      end
    end
  end

这很慢 - 我试图在FileImporter工作者中限制对ActiveRecord的调用,所以我假设它是因为我从S3流式传输文件。它没有足够快地处理行来构建队列,所以我从不使用我的所有工作线程(通常在35个可用线程中的15-20个之间有一些活动。我已尝试拆分这个工作并一次向一个中间工作者提供一行100,然后以更加平行的方式创建ImportValidator工作,但这并没有好得多。

所以我的问题是,完成这样的任务的最好/最有效的方法是什么?

1 个答案:

答案 0 :(得分:1)

你有可能拥有20个线程的100%CPU。你需要另一个dyno。