Performance in Ruby on Rails Applications

This post outlines a number of things to consider when evaluating the performance of Ruby on Rails applications.
Asad Ahmed
Asad Ahmed
November 28, 2022

Many developers place a great importance on performance, and many web frameworks, programming languages, databases, and other technologies make performance one of their main selling points. Interestingly, this isn't typically the first thing that web application developers take into account because creating a product or service faster is of higher priority. In particular, when developing Rails applications, performance is frequently disregarded until it becomes an issue. But if left unattended for too long, getting better can be quite difficult. It is beneficial to constantly evaluate performance and search for bottlenecks or design decisions that are causing delays.

Performance Areas

There are few areas to give special attention to when considering performance in a Ruby on Rails application.

Page-load time

This is where the performance of an application is most felt because it is where the end user interacts with your application. This area can either make or break your application as users do not linger around when the page takes too long to load.

Application start-up time

This area is more about developer experience (DX) than user experience (UX). Although it does not affect the end user, a slow start-up is usually a sign that an application is loading more data than necessary. A poor DX can lead to longer testing phases and lost productivity in the long run.

Measuring Performance

Inspecting the logs/console

Inspecting the log will greatly help in determining the source of several performance issues that the application may be experiencing. Usually, the logs containing all the details are displayed on the console if the application is running in the development environment. If the application is running in production, it would be found wherever your logging files are stored. Inspecting your logs and console, especially in development, can tell a lot about how the database is being queried and how many queries are being sent. This can help in identifying areas that need query optimization or what areas of asynchronous querying(only on Rails 7) can be employed to cut down the number of queries to the database.

Application Response Time

An effective application's response time should never exceed half a second (500 milliseconds). If you cross that line, you should look into ways to delegate some of the processing to asynchronous workers, reduce the number of queries, or cache data.

Query Count

If the log for a single request is filled with a lot of database queries that can often be a red flag in identifying a performance bug. A normal request would ideally have somewhere between 1-4 queries (minimum) but in real world applications, the queries would be much higher than that number. If your console is overflowing with queries when pages load, it is time to optimize how you query the database. We will see some techniques and best practices to optimize database queries using rails in later sections of this post.

Optimizing Query Strategies

As your project progresses and grows, it is unavoidable that your database as well as your code base grows. As a result, your queries might be taking longer than needed. There are two primary ways to improve querying the database to gain speed and performance.

The first method is indexing your database. An index is simply a pointer to information in a table. An index in a database resembles a book's back matter index quite a bit. Indexes are a great tool a database uses in the background to speed up querying. By giving users a way to rapidly search up the information they need, indexes power queries. When it comes to indexes in the database, you should index any column that you often use for sorting. The indexing of foreign keys is necessary for faster association lookups. When you add indices to your tables, the application improves performance without requiring any changes to the model.

Reducing the number of queries made is another way to improve the total time of a request. This is achieved by selecting more data in a single query rather than running multiple queries.

Scopes with includes

When the parent object loads, the detected child entries are eagerly loaded using the includes query method. Let's take an example of an e-commerce web app where we have the following models: Orders, Items, Status

Just for simplicity, let's use the console to create status (paid or not) for each order:

Now let's see what happens when we fetch an Order sample from the database:

See how it queries the status table once for each item? Imagine how expensive this query would get if we had thousands of orders. Now let's improve this using the includes keyword:

No matter how many items there are, the first instruction runs three queries, and the select line doesn't need to execute any more.

Using default_scope

A default scope can be set up on the model if an object will always load its child records. The children will then be eagerly loaded by every query.

Using the prior example as a guide, suppose we always want the items for an order to be loaded. Instead of having to remember to add include: :items to all finder calls, add the following to the Order model:

The corresponding items will be included in Order.find(1) once the above has been included in the model. Therefore, another database query is no longer required if you need the items for that order.

In conclusion, in addition to the ways we described above, there are many ways to improve the performance of a Rails application that are not included in this post. It is always recommended that performance of the application be taken into consideration during the early stages of the application design and development. Failure to do so could lead to costly adjustment or consumption of unnecessary memory and other resources down the road as the application grows.

Caching

A cache is a high-speed data storage layer that caches a set of data so that future requests for that data can be fulfilled more quickly than accessing the data's primary storage location. Caching can drastically improve page-loading times.

Are you excited about building highly performant applications? If so, we'd love to meet you. Have a look at our open positions!

About the author

Asad Ahmed

Asad is a Full Stack Developer based out of Kitchener, ON.