Skip to main content

Ruby 3.5 Preview: What Matters for Rails in Production

Ruby upgrades help, but your slow endpoints are still slow for the same reasons: query shape, object allocations, and cache pressure.

Ruby 3.5 was released as a preview; Ruby 4.0 shipped. Here’s what actually matters for Rails apps: what won’t change, what can break, and where SQL/ActiveRecord fixes beat runtime upgrades.

A
Raza Hussain
· Updated: · 4 min read · 138
Published in Ruby & Rails Core
Ruby 3.5 Preview: What Matters for Rails in Production

Ruby 3.5 Game Changers for Rails Apps

Ruby upgrades help, but your slow endpoints are still slow for the same reasons: query shape, object allocations, and cache pressure.

Start with a real production bug

We upgraded Ruby expecting our slowest admin endpoint to “just get faster”.

It didn’t.

The endpoint was a classic Rails anti-pattern: it loaded thousands of ActiveRecord objects, selected *, and then did Ruby-side work that should have been pushed down into SQL.

The runtime improved. The endpoint stayed slow.

The wrong mental model

“If Ruby gets faster, my Rails app gets faster.”

Ruby upgrades can improve:

  • object allocation costs
  • JIT quality on CPU-heavy code paths
  • GC behavior in certain workloads

But most Rails app latency isn’t CPU-bound Ruby. It’s:

  • database time
  • object instantiation + memory churn
  • N+1s and oversized result sets

So if your app is slow because you’re pulling too much data and allocating too many objects, a faster runtime doesn’t change the shape of the problem.

First: reality check on Ruby 3.5

Ruby 3.5 was released as 3.5.0-preview1 (a preview release). Ruby 4.0.0 was released later as the stable yearly release.

So if your blog post is written as if Ruby 3.5 is already stable and “transforms” Rails apps, it will age badly.

The productive framing is:

  • Preview releases: good for testing compatibility and measuring trends
  • Stable releases: what you actually upgrade production to

The naive approach (and why it fails)

Here’s the pattern that shows up everywhere:

# app/controllers/admin/orders_controller.rb
orders = Order
  .where(status: "open")
  .order(created_at: :desc)
  .limit(5_000)

orders.each do |order|
  # Ruby-side work
end

Generated SQL (verified):

SELECT "orders".*
FROM "orders"
WHERE "orders"."status" = 'open'
ORDER BY "orders"."created_at" DESC
LIMIT 5000;

Why this stays slow even on a faster Ruby

  • SELECT * pulls columns you don’t need
  • ActiveRecord instantiates thousands of Ruby objects
  • allocations + GC dominate the request
  • the DB may still be doing extra work if you don’t have the right index

A runtime upgrade can shave some CPU time, but it won’t:

  • remove object creation
  • remove SELECT *
  • remove missing indexes
  • reduce network I/O

The correct approach: fix the query shape first

1) Select only what you need

If you need a subset of fields:

orders = Order
  .where(status: "open")
  .select(:id, :total_cents, :created_at)
  .order(created_at: :desc)
  .limit(5_000)

Generated SQL (verified):

SELECT "orders"."id", "orders"."total_cents", "orders"."created_at"
FROM "orders"
WHERE "orders"."status" = 'open'
ORDER BY "orders"."created_at" DESC
LIMIT 5000;

This reduces:

  • row size transferred from Postgres
  • Ruby object size
  • overall memory churn

2) If you don’t need models, don’t instantiate models

If you’re building a report or exporting rows, prefer pluck:

rows = Order
  .where(status: "open")
  .order(created_at: :desc)
  .limit(5_000)
  .pluck(:id, :total_cents, :created_at)

Generated SQL (verified):

SELECT "orders"."id", "orders"."total_cents", "orders"."created_at"
FROM "orders"
WHERE "orders"."status" = 'open'
ORDER BY "orders"."created_at" DESC
LIMIT 5000;

This is a “Ruby upgrade proof” optimization: it helps on every Ruby version.

3) Add the index that matches the query

If you frequently do:

WHERE status = 'open'
ORDER BY created_at DESC
LIMIT ...

A practical index is:

CREATE INDEX CONCURRENTLY index_orders_on_status_created_at
ON orders (status, created_at DESC);

This is the kind of change that often beats any runtime win.

Where Ruby improvements still matter

Once you fix query shape, you’ll see where Ruby changes help:

  • request middleware overhead
  • CPU-heavy JSON rendering
  • background jobs doing parsing/transforms
  • hot Ruby loops (rare, but real)

At that point, enabling/validating JIT improvements can become meaningful — because you’ve removed the dominant bottleneck.

Edge cases and production pitfalls

  • select can break code that assumes missing attributes exist (e.g., calling order.user_id when you didn’t select it).
  • pluck bypasses type casting in some paths and returns raw values; treat it as “data extraction”, not a model.
  • Indexes aren’t free: they cost write amplification and disk. Add them where query frequency justifies it.
  • Preview versions are not upgrade targets: treat them as compatibility probes and benchmark baselines.

Rule of thumb

Upgrade Ruby for compatibility and long-term health.
Fix SQL and allocation patterns for speed.

If your Rails app is slow, start with:

  1. query shape (columns, limits, joins)
  2. index alignment
  3. object instantiation (pluck/select/batches)
  4. only then: runtime/JIT tuning

Was this article helpful?

Your feedback helps us improve our content

Be the first to vote!

How We Verify Conversions

Every conversion shown on this site follows a strict verification process to ensure correctness:

  • Compare results on same dataset — We run both SQL and ActiveRecord against identical test data and verify results match
  • Check generated SQL with to_sql — We inspect the actual SQL Rails generates to catch semantic differences (INNER vs LEFT JOIN, WHERE vs ON, etc.)
  • Add regression tests for tricky cases — Edge cases like NOT EXISTS, anti-joins, and predicate placement are tested with multiple scenarios
  • Tested on Rails 8.1.1 — All conversions verified on current Rails version to ensure compatibility

Last updated: January 16, 2026

Try These Queries in Our Converter

See the SQL examples from this article converted to ActiveRecord—and compare the SQL Rails actually generates.

138
R

Raza Hussain

Full-stack developer specializing in Ruby on Rails, React, and modern JavaScript. 15+ years upgrading and maintaining production Rails apps. Led Rails 4/5 → 7 upgrades with 40% performance gains, migrated apps from Heroku to Render cutting costs by 35%, and built systems for StatusGator, CryptoZombies, and others. Available for Rails upgrades, performance work, and cloud migrations.

💼 15 years experience 📝 12 posts

Responses (0)

No responses yet

Be the first to share your thoughts