---
title: "RR006 – Use find_each / in_batches for Bulk Iteration"
impact: high
impactDescription: "find_each limits memory to batch size (1000 rows) instead of loading all records at once, preventing OOM in background jobs and rake tasks."
tags: [ruby, rails, performance, database]
---

# RR006 – Use find_each / in_batches for Bulk Iteration

## Rule

When iterating over all records (in jobs, scripts, migrations, Rake tasks), always use `find_each` or `in_batches` instead of `all.each` or `where(...).each`.

## Why

`Model.all.each` fetches all matching rows in one SQL query and holds them all in memory. For large tables this causes OOM. `find_each` internally runs batched queries (`LIMIT 1000 ORDER BY id`) and yields one record at a time.

## Wrong

```ruby
# Loads every user into memory at once
User.all.each do |user|
  UserMailer.renewal(user).deliver_later
end

# Same problem with a where scope
Order.where(status: 'pending').each { |o| processOrder(o) }
```

## Correct

```ruby
# find_each: yields one record at a time, batched under the hood
User.find_each(batch_size: 500) do |user|
  UserMailer.renewal(user).deliver_later
end

# in_batches: yields an ActiveRecord::Relation per batch (useful for bulk updates)
Order.where(status: 'pending').in_batches(of: 200) do |batch|
  batch.update_all(processed: true)
end

# start: / finish: let you resume from a checkpoint
User.find_each(start: last_processed_id) do |user|
  # ...
end
```

## Notes

- `find_each` requires a primary key — does not work with `select` that omits the PK.
- Avoid adding an `ORDER BY` other than PK with `find_each`; use `in_batches` if you need custom ordering.
- For read-only iteration on PostgreSQL, consider `cursor()` from the `activerecord-cursor-pagination` gem for server-side cursors.
