How to Improve Ruby on Rails Performance in an Existing App
Archie Norman
If your Ruby on Rails application feels slower than it used to, that doesn’t automatically mean you need a rebuild.
Often, the application has simply changed. There are more users, more records, more integrations and more business rules than there were when the original technical decisions were made. Queries that once ran unnoticed can become bottlenecks, while pages that were easy to render with a small dataset can struggle at a larger scale.
The good news is that mature Rails applications can usually be improved in stages. The important part is finding out where the time is actually going before changing code, infrastructure or architecture.
Why Ruby on Rails applications get slower over time ⏳
A Rails app can perform perfectly well when it launches and still develop performance problems later.
That’s because application performance depends on much more than the framework itself. As a product grows, you may be asking the same code to deal with significantly more data and complexity. 😵💫
Common causes include:
- Database tables containing far more records than they did originally.
- Pages making too many database queries.
- Queries loading far more data than the page actually needs.
- Missing or unsuitable database indexes.
- Expensive calculations being repeated on every request.
- Personalised content preventing otherwise reusable content from being cached.
- Emails, API calls or file processing happening during a user request.
- Large images, video or frontend assets slowing down delivery.
- Infrastructure that no longer matches the traffic or workload of the application.
This is why performance optimisation should rarely begin with assumptions such as “Rails is slow” or “we need bigger servers”. You need evidence first.
Measure Rails performance before changing anything 📊
The first job is to establish a baseline.
A developer’s laptop isn’t a realistic representation of production. It has a fast local connection, immediate access to the development database and usually a fraction of the data found in the live application.
Throttling your browser connection or CPU can be useful when reproducing a problem, but production monitoring tells you much more. You want visibility into things such as:
- Which requests and controller actions are slow.
- How response times change under real traffic.
- How many database queries a request makes.
- Which SQL queries consume the most time.
- Whether memory or CPU usage spikes.
- Whether background jobs are backing up.
- Whether an external API or another service is responsible for the delay.
Rails itself provides instrumentation hooks through Active Support Instrumentation, including events around controller processing and SQL queries. Rails 8.1 also introduced structured event reporting for application-level observability. An application performance monitoring tool can then help turn that information into something your team can investigate over time.
The important principle is simple: optimise the slowest important thing. ⏱️
Start with database queries 🗄️
For many Rails applications, the database is one of the first places worth investigating.
Rails makes working with relational data pleasantly straightforward through Active Record. That productivity is useful, but seemingly harmless application code can generate much more database work than expected. 🤯
Look for N+1 queries
An N+1 query happens when the application loads a collection and then makes an additional database request for an associated record for every item in that collection.
Rails supports eager-loading approaches such as includes, preload and eager_load to reduce unnecessary database round trips. The official Active Record query guide explains the differences.
Tools can help too. The Bullet gem can identify N+1 queries, unnecessary eager loading and opportunities for counter caches while you develop and test an application.
Only retrieve the data you actually need
Sometimes the problem isn’t the number of queries. It’s how much work each query performs.
Loading full Active Record objects when you only need a few values can increase memory use. Complex relationships can also result in several database round trips where a carefully structured query would do the job more efficiently.
Our guide to making Active Record code more efficient covers techniques such as pluck, select and asynchronous loading in more detail.
There isn’t one universally “best” query style. Readability still matters, and shaving a tiny amount off an infrequent query may not justify making the code much harder to maintain. Measure first, then optimise the places where the difference matters. 📈
Review your database indexes
Indexes can make a major difference when the database repeatedly needs to find a small number of records within a much larger table.
They aren’t free, though. An index takes storage and creates additional work when data changes, so adding indexes everywhere isn't a sensible optimisation strategy. The PostgreSQL documentation recommends using them selectively based on how the database is actually queried.
Use caching where it removes repeated work ⚡
If the application is repeatedly generating the same result, caching can avoid doing that work again.
Rails includes several caching mechanisms, including fragment caching for parts of a rendered page and lower-level caching for reusable data or expensive calculations. The Rails caching guide covers the available options and cache invalidation behaviour.
One useful question is whether everything on a page genuinely needs to be personalised.
Imagine a page containing a large catalogue of records alongside a small amount of user-specific information. If the entire rendered result depends on the current user, you may lose the ability to cache most of the page. 🫠
Sometimes a better approach is to separate the two concerns:
- Render and cache the content that is common to everyone.
- Load or apply the smaller amount of personalised state separately.
That can turn an expensive repeated request into mostly reusable work.
Caching does introduce another problem: deciding when cached information should expire. A fast answer that is out of date can be worse than a slower correct one, so the cache strategy needs to match how frequently the underlying information changes.
Precompute expensive data instead of rebuilding it on every request 🧮
Some business logic is simply expensive to calculate.
You might have a booking system calculating availability from opening hours, staff schedules, holidays and existing reservations. Or an operational dashboard may need to combine large amounts of historical data before displaying a summary.
If that answer doesn’t need to be recalculated from scratch every time someone opens the page, consider calculating it ahead of time and storing the result.
You can then refresh that data when something relevant changes or on an appropriate schedule. The user-facing request becomes much simpler because the difficult work has already happened. 🪄
This can be especially useful in established products where replacing the entire underlying architecture would be disproportionate. Sometimes the best performance improvement is a focused change in where and when work happens.
Move non-critical work out of the request cycle ⚙️
A user clicking a button usually shouldn’t have to wait for every related task to finish before receiving a response.
At mmtm, we find operations such as these are good candidates for background processing:
- Sending emails.
- Generating reports or exports.
- Processing uploaded files.
- Synchronising data with another system.
- Running maintenance tasks.
- Performing calculations that don’t affect the immediate response.
Rails provides Active Job for declaring and running background work. Moving genuinely asynchronous tasks away from the request-response cycle can make the application feel much more responsive to the person using it.
The trade-off is operational complexity. Background jobs need monitoring, retries and sensible failure handling. Moving work into a queue only helps if you can trust that queue to finish the job.
Don’t forget the frontend and content delivery 🌐
Not every slow Rails page is being held up by Ruby or SQL.
A server might generate the HTML quickly while the browser still spends several seconds downloading oversized images, video, JavaScript or other assets.
For media-heavy applications or products with geographically distributed users, a content delivery network can move cacheable files closer to the people requesting them and reduce work for the main application server.
We’ve written separately about using AWS CloudFront to improve content delivery in a Rails application, including its use alongside Active Storage and Amazon S3.
This is why performance work needs to look at the whole journey rather than stopping once the Rails request itself appears healthy.
Keep Rails and its dependencies healthy 🛠️
An old Rails version isn’t automatically the cause of a performance problem, and an upgrade isn’t a substitute for investigating slow queries or poor application design.
However, keeping a mature application reasonably current makes ongoing improvement easier. You benefit from current framework features, dependency compatibility and better tooling, while avoiding a growing backlog of upgrade work.
Rails 8.1, for example, includes additions around background job continuations and structured event reporting that can help teams operate and diagnose modern Rails applications. We cover the practical implications in our guide to upgrading a web application to Rails 8.1.
Rails performance optimisation should be ongoing 🔄
Performance isn’t something you fix once.
A page that performs well with 20,000 records may behave very differently with 2 million. A feature that was rarely used at launch might later become one of the busiest parts of the product.
That makes monitoring and regular review part of sensible application maintenance.
At mmtm, our Ruby on Rails support and maintenance work includes monitoring, performance improvements, framework upgrades and ongoing feature development. The aim is to spot changes in the health of an application before they become disruptive.
When a slow Rails app needs an audit rather than a quick fix 🔍
Sometimes the bottleneck is obvious: one slow query, an unoptimised image or a missing index.
Sometimes it isn’t.
If the application is several years old, has changed development teams, runs on ageing dependencies or has several performance problems at once, changing isolated pieces of code can create more risk than progress.
Signs that a broader review may be useful include:
- You don’t have reliable production performance monitoring.
- Different parts of the app become slow without an obvious pattern.
- Queries time out as data volumes increase.
- The team is unsure which parts of the application are safe to change.
- The Rails version or major dependencies are significantly behind.
- Performance work repeatedly treats symptoms without finding the underlying cause.
In that situation, a technical review gives you a clearer picture before you start spending money on fixes. Our Improve a Product service always begins with a codebase audit to understand the infrastructure and operational context, then prioritising improvements around performance, stability, security and future development.
The goal isn’t to make every request as fast as technically possible. It’s to make the application fast enough, reliable enough and maintainable enough for the people and business depending on it.
Martin Dick
Co-Founder, mmtm
Is your Rails app getting slower as it grows?
A slow application doesn't necessarily need rebuilding. Often, the right combination of monitoring, query improvements, caching and focused technical work can make an established Rails product much healthier.
If you're not sure where the bottleneck is, we can review the application, identify the issues creating the most drag and help you prioritise the improvements that are actually worth making.
Improve Your Rails AppFrequently Asked Questions
Why is my Ruby on Rails application slow?
There isn't one universal cause. Common issues include inefficient database queries, N+1 queries, missing indexes, repeated calculations, poor caching, slow third-party services, overloaded background jobs, large frontend assets or infrastructure that no longer matches the application's workload. Measure the live application first so you can identify the real bottleneck.
Does a slow Rails app need to be rebuilt?
Usually not. Many mature Rails applications can be improved incrementally through query optimisation, caching, background processing, infrastructure changes, framework upgrades and targeted refactoring. A rebuild should be considered only when the wider condition of the product makes it the more sensible option.
How can I find N+1 queries in Rails?
Start by reviewing Rails logs and performance monitoring for actions making unexpectedly large numbers of database queries. Rails' eager-loading features can resolve many N+1 problems, while tools such as Bullet can automatically flag common cases during development and testing.
When should I use caching in a Rails application?
Caching is useful when your application repeatedly generates the same content or recalculates the same data. Good candidates include reusable view fragments, expensive calculations and data that changes less often than it is read. The main consideration is cache invalidation: you need a reliable way to stop stale information being served when the underlying data changes.
Can Ruby on Rails handle large applications?
Yes. Application scale depends on much more than the framework: database design, queries, caching, background jobs, infrastructure, monitoring and the shape of the product all matter. Rails provides mature tools for each of these areas, but they still need to be used appropriately as the application grows.
How often should Rails application performance be reviewed?
There isn't a fixed timetable that suits every product. Business-critical applications should have continuous monitoring, with deeper reviews when traffic, data volumes or product functionality change significantly. Performance should also be checked after substantial releases so regressions can be caught early.
Not Sure What To Read Next? Here's What We Recommend...
- Ruby on Rails
- Performance Optimisation
- Web Development
- Support & Maintenance