处理50万条记录致服务器崩溃,如何分批处理?
优化大规模数据处理Rake任务的方案
你的问题核心在于一次性加载50万条记录到内存,加上Rails默认的查询缓存、未及时释放的数据库连接,最终导致内存和CPU耗尽。下面是几个无需依赖Sidekiq的可行优化方案,按优先级排序:
1. 强制分批加载记录(必做)
Rails的each会把所有符合条件的记录一次性实例化为ActiveRecord对象,直接占满内存。改用find_each或find_in_batches,它们会自动分批查询并释放内存:
task get_new_data: :environment do # 关闭查询缓存,避免批量处理时缓存无效数据占用内存 Model.uncached do # 按1000条一批处理,可根据服务器配置调整大小 Model.where('column IS NOT NULL').order('id DESC').find_each(batch_size: 1000) do |d| begin puts "...processing record #{d.id}..." $stdout.flush # 强制刷新终端输出,解决前期无内容的问题 api_call = API::Call(...) d.assign_attributes(attr1: api_call.data1, attr2: api_call.data2) d.save!(validate: false) puts "...record #{d.id} updated..." $stdout.flush # 随机sleep,避免固定间隔导致的API/服务器压力集中 sleep(0.3 + rand(0.4)) rescue => e # 记录错误,避免单条失败中断整个任务 Rails.logger.error "Failed to process record #{d.id}: #{e.message}" next end end end end
2. 释放数据库连接(必做)
如果任务运行时间很长,ActiveRecord会一直占用数据库连接,可能导致连接池耗尽。每处理完一批后手动释放连接:
Model.where('column IS NOT NULL').order('id DESC').find_in_batches(batch_size: 1000) do |batch| batch.each do |d| # 处理逻辑... end # 释放当前线程的数据库连接 ActiveRecord::Base.clear_active_connections! end
3. 批量更新优化(可选)
如果API返回的数据可以批量获取,或者更新逻辑无依赖,可以用update_all减少SQL查询次数。比如先收集一批需要更新的id和对应数据,再批量执行:
Model.where('column IS NOT NULL').order('id DESC').find_in_batches(batch_size: 2000) do |batch| updates = [] batch.each do |d| api_call = API::Call(...) updates << { id: d.id, attr1: api_call.data1, attr2: api_call.data2 } end # 批量生成UPDATE语句,减少数据库交互 updates.each_slice(100) do |slice| Model.transaction do slice.each do |update| Model.where(id: update[:id]).update_all(attr1: update[:attr1], attr2: update[:attr2]) end end end ActiveRecord::Base.clear_active_connections! sleep(1) # 批量处理后适当延长休息时间 end
4. 服务器资源保护(可选)
如果服务器配置较低,可以限制任务的CPU使用,比如用nice命令启动Rake任务,降低进程优先级:
nice -n 10 bundle exec rake get_new_data
原任务崩溃的核心原因
- 一次性加载50万条ActiveRecord对象,内存占用随处理持续增长,最终耗尽
- Ruby的stdout默认缓冲,导致前期无输出,给人任务卡住的错觉
- 长时间运行未释放数据库连接,连接池耗尽后数据库响应变慢,加剧CPU占用
内容的提问来源于stack exchange,提问作者user984621
相关产品推荐
相关产品推荐

