# Essential ways to make your Rails faster

There is no reason to keep the application slow. If we have room to improve the performance of our application without any drawback, we should do. It makes our users happier, and your application becomes attractive and prevents people from leaving for alternatives due to the performance issue.

But what should we do? Ideally, we should carefully measure the profile of the application runtime and detect the bottleneck to be improved. As Donald Knuth said, the premature optimization is the root of all evil. We must not do any optimization blindly. That is the fundamental principle.

But I understand the situation; we may want to quickly know the essential tips to apply to every kind of web application. That is the general method simply applicable regardless of the type of application. This is the list of a few tips every Rails developer should know to keep your application performant. Please keep them in your mind every time your write your Rails application.

# Data Schema

Before we begin the journey, let’s define our database schema first as our example.

CREATE TABLE states (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE cities (
id bigint(20) NOT NULL AUTO_INCREMENT,
state_id bigint(20) DEFAULT NULL,
name varchar(255) NOT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id),
KEY index_cities_on_state_id (state_id),
CONSTRAINT fk_rails_cc74ecd368 FOREIGN KEY (state_id) REFERENCES states (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE offices (
id bigint(20) NOT NULL AUTO_INCREMENT,
city_id bigint(20) DEFAULT NULL,
name varchar(255) NOT NULL,
address varchar(255) DEFAULT NULL,
zip_code varchar(255) DEFAULT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id),
KEY index_offices_on_city_id (city_id),
CONSTRAINT fk_rails_52308f6f48 FOREIGN KEY (city_id) REFERENCES cities (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


Our company has several offices across states in the country. An office locates in a city in a state. Hence we have associates between offices and cities, cities and states. Let’s consider the case we want to generates the list of all office entities with the city and state name where it locates.

# Avoid N+1 Query

We can naively do so as follows.

Office.all.each do |office|
puts office.city.name
end


This code exactly does we want. But it’s slow because it issues redundant queries to the backend database system. Offices.all get the list of all office entities by running 1 SQL. Let’s say we get 100 offices here. Unfortunately, 100 queries to get each city entity follows. In total, we need to run 100 + 1 queries. If we put a code like office.city.state.name, the number exponentially grows, as you can imagine. That’s a problem with the N+1 query.

But no worries, we have a quick way to fix that. include

Office.include(:city).all.each do |office|
puts office.city.name
end


This code issues a query to get all city entities associated with the office, not iterating one by one. That means we only run 1+1 queries even as a whole.

SELECT cities.* FROM cities
WHERE (cities.id IN (1,2,3,4,5,6,7,8,9,10))


# Use pluck (and join)

Another performance problem likely to occur is the slowdown due to fetching many columns. If we have an entity having many attributes, getting these columns is time-consuming, and instantiation of ActiveRecord matters. We can want to pick up only the specific columns without instantiating ActiveRecord for every record when we only print the result (e.g., generate CSV).

pluck enables us to issue a SQL fetching only specified columns from the underlying table. We can avoid SELECT * like query in short. We also need to use join instead of include to specify the column in the external tables (i.e., cities). The following code only issues 1 query to join every necessary table with the selected columns. It’s a pretty bare minimum query to accomplish what we want.

columns = [
'offices.name',
'cities.name'
]
Office.joins(:city).pluck(*columns).each do |office|
puts office.city.name
end