Introduction
When you need to process thousands of records, you cannot load them all at once. Batch processing patterns split large datasets into manageable chunks.
Key Concepts
- Fan-out Pattern: One coordinator splits work into many parallel jobs.
- find_each / each_slice: Active Record methods for chunked processing.
Real World Context
Sending newsletters to 100,000 users, re-indexing search, or migrating data all require batching.
Deep Dive
Fan-Out Pattern
rubyclass BulkEmailCoordinatorJob < ApplicationJob def perform(campaign_id) campaign = Campaign.find(campaign_id) campaign.subscribers.pluck(:id).each_slice(100) do |batch_ids| BulkEmailBatchJob.perform_later(campaign_id, batch_ids) end end end class BulkEmailBatchJob < ApplicationJob def perform(campaign_id, user_ids) campaign = Campaign.find(campaign_id) User.where(id: user_ids).find_each do |user| CampaignMailer.send_campaign(campaign, user).deliver_now end end end
Tracking Progress
rubyclass BatchImportCoordinatorJob < ApplicationJob def perform(import_id) import = Import.find(import_id) rows = CSV.read(import.file.path, headers: true) import.update!(total_rows: rows.size, processed_rows: 0, status: :processing) rows.each_slice(50).with_index do |batch, index| BatchImportChunkJob.perform_later(import_id, batch.map(&:to_h), index) end end end
Common Pitfalls
- Loading all records into memory — Use
pluck(:id).each_sliceinstead. - Batch size too large or small — 50-500 is usually the sweet spot.
Best Practices
- Use a coordinator job to split work — Easy to monitor and retry.
- Track progress in the database — Let users see how far along the operation is.
Summary
- Fan-out patterns split large tasks into parallel batch jobs.
pluck(:id).each_slice(N)avoids loading everything into memory.- Track progress with database counters.
- Batch sizes of 50-500 balance throughput and reliability.
Code Examples
ruby
# Fan-out: split 10,000 users into batches of 100
User.subscribed.pluck(:id).each_slice(100) do |batch_ids|
SendNewsletterBatchJob.perform_later(newsletter.id, batch_ids)
end