Services About Us Why Choose Us Our Team Development Workflow Technology Stack Case Studies Portfolio Blog Free Guides Shopify Audit ($499) Estimate Project Contact Us
Back to Insights
Ruby on Rails Feb 27, 2026 14 min read

Rails Active Record Best Practices: Query Optimization, Indexing & Batching (2026)

The authoritative guide to Active Record mastery in Ruby on Rails. Partial indexes, subqueries vs joins, batch processing for millions of records, and optimistic vs pessimistic locking.

TV
TechVinta Team
Specialized in Rails, React, Marketplace & Sharetribe Flex Architecture
Verified Technical Guide
Rails Active Record Best Practices: Query Optimization, Indexing & Batching (2026)

Writing High-Performance Active Record Queries

Active Record is one of the greatest productivity engines in software development, but it abstracts away the database so effectively that developers frequently forget they are interacting with an ACID relational database. Below are the core principles for writing scalable Active Record code.

1. Memory-Safe Batch Processing for Millions of Records

Calling User.all.each on a table with 500,000 users instantiates half a million Ruby ActiveRecord objects simultaneously, consuming several gigabytes of RAM and triggering massive Garbage Collection stalls:

# ❌ CRASHES PRODUCTION: Loads all rows into RAM at once
Order.where(status: :completed).each { |order| order.send_receipt! }

# ✅ OPTIMIZED: Loads 1,000 records at a time using keyset pagination
Order.where(status: :completed).find_each(batch_size: 1000) do |order|
  order.send_receipt!
end

# ✅ FASTEST FOR BULK UPDATES: Updates in SQL without instantiating Ruby models
Order.where(status: :pending).where("created_at < ?", 7.days.ago).in_batches(of: 1000) do |batch|
  batch.update_all(status: :expired)
end

2. Selective Column Fetching: pluck and select

If you only need an array of IDs or emails, don't instantiate full ActiveRecord model objects:

# ❌ SLOW: Instantiates 10,000 User objects in Ruby memory
emails = User.where(active: true).map(&:email)

# ✅ FAST: Returns raw Array of strings directly from PostgreSQL driver (25x faster)
emails = User.where(active: true).pluck(:email)

3. Concurrency & Race Condition Prevention: Pessimistic Locking

When handling payment balance debits, inventory deductions, or booking slot reservations, simultaneous HTTP requests can read the same balance and overspend. Use with_lock to issue SELECT ... FOR UPDATE:

class Wallet < ApplicationRecord
  def deduct_balance!(amount)
    with_lock do # Acquires row-level lock inside database transaction
      raise InsufficientFundsError if balance < amount
      update!(balance: balance - amount)
    end
  end
end

Looking for Dedicated Senior Rails Engineers?

Our US-aligned engineering teams write production-grade Rails and PostgreSQL code for venture-backed startups and growing SaaS companies.

Hire Rails Developers in USA →

Frequently Asked Questions

What is the difference between find_each and in_batches in Rails?
find_each yields individual model records one by one in batches of 1,000. in_batches yields an ActiveRecord relation representing the batch, allowing bulk database operations like update_all or delete_all without instantiating individual Ruby objects.

When should I use counter_cache in Active Record?
Use counter_cache: true whenever you frequently display association counts (e.g. category.products_count) to eliminate costly SELECT COUNT(*) queries in views.

What is the difference between optimistic and pessimistic locking?
Optimistic locking uses a lock_version column and raises an error if two transactions attempt to save changes to the same version. Pessimistic locking locks the database row immediately via FOR UPDATE until the transaction commits.

How do I fix database deadlocks in Active Record?
Deadlocks occur when two transactions acquire locks on multiple records in differing orders. Always sort record IDs before acquiring locks (e.g. [user_a, user_b].sort_by(&:id).each(&:lock!)) so transactions lock resources in identical sequence.

Share this article:
TV

Written by TechVinta Team

We are a full-stack development agency specializing in Ruby on Rails, React.js, Vue.js, Flutter, Shopify, and Sharetribe. We write about web development, DevOps, and building scalable applications.

Keep Reading

TechVinta Assistant

Online - Ready to help

Hi there!

Need help with your project? We're online and ready to assist.

🍪

We use cookies for analytics to improve your experience. See our Cookie Policy.