所以我试图通过Heroku Worker实例上的Sidekiq后台作业处理来处理CSV文件。虽然我可以完成这个过程,但我觉得它可以比我目前正在做的更快更有效地完成。这个问题包含两个部分 - 首先是正确设置数据库池,其次是如何优化流程。
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
---
:concurrency: 5
staging:
:concurrency: 5
production:
:concurrency: 35
:queues:
- [default, 1]
- [imports, 10]
- [validators, 10]
- [send, 5]
- [clean_up_tasks, 30]
- [contact_generator, 20]
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工作,但这并没有好得多。
所以我的问题是,完成这样的任务的最好/最有效的方法是什么?
答案 0 :(得分:1)
你有可能拥有20个线程的100%CPU。你需要另一个dyno。