Often improving performance means saving money, and vice versa - when looking into how to save money, we can find ways to optimize the performance of Ruby on Rails apps. We care deeply about efficient and economic work, so we often come up with solutions to save money, development time and make our jobs easier.

Below we present different ways of increasing cost efficiency in Ruby on Rails apps - from speeding up specs in Ruby to decreasing requests in a monitoring and logging service.

1. Speed up Ruby specs that must touch the database

Tests which don’t communicate with the database are faster to run, but there are cases when we really need the database to run the specs - which are necessary for following best practices for quality assurance in back-end development.

Here is an example of specs which must create records in the DB. We have one model, called FollowUp, frequently used across the whole application. In our RSpec test suite we are using the FactoryGirl (currently re-named to FactoryBot) gem to create test data in the database.

The model has a factory which look like this:

FactoryGirl.define do
  factory :follow_up do
    job
    contact
    employee { FactoryGirl.create(:email_contact, user: FactoryGirl.create(:employee)) }
    phone { FFaker::PhoneNumber.short_phone_number }
  end
end

It basically has a few associations like contacts, job and employee which are created whenever the FollowUp model is created. The factories for other models look like the following:

FactoryGirl.define do
  factory :job do
    category
    department
  end
end


FactoryGirl.define do
  factory :department
    name { 'Name' }
    company
  end
end

As you may have noticed, creating one instance of follow_up factory inserts multiple records to the database. Such configuration is overkill for tests unless you need all associations in each of them. Let’s consider the following example:

require 'spec_helper'

describe FakeClass do
  let!(:follow_up) { FactoryGirl.create :follow_up }

  it 'does something' do
  end

  it 'does something' do
  end

  it 'does something'
  end
end

We have three blank tests - how many records did we create in the database during execution of these tests? 27!

How do we detect how many factories our Ruby test is using? We can use the FactoryProf feature from the test-prof gem. After adding the gem to our Gemfile, we can run our test with the FPROF=1 flag. It will output a summary of the total and top-level factories used in the test. After removing unneeded associations we are still creating 3 records in the database, while we only need only one record for all test examples.

Solution?

  • Remove optional associations from the factory definition because the base factory should have only those attributes or associations for which the model needs to pass the validation (you can use traits for building an object with extra attributes or associations - i.e. FactoryGirl.create(:follow_up, :with_job, :with_candidate)). If your model needs associations to pass the validation, you can consider moving them to the form objects.
  • Moving the creation part to before(:all) block (you can use the let_it_be helper from test_prof library). Keep in mind this will work only for objects that are not mutated in the tests - otherwise it can lead to order dependent tests and random failures.

Let’s check the results after applying the above solution to our initial example:

FactoryGirl.define do
  factory :follow_up do
    phone { FFaker::PhoneNumber.short_phone_number }
  end
end


require 'spec_helper'

describe FakeClass do
  let_it_be(:follow_up) { FactoryGirl.create :follow_up }

  it 'does something' do
  end

  it 'does something' do
  end

  it 'does something'
  end
end

Note: Before doing this you have to add require 'test_prof/recipes/rspec/let_it_be' to your spec_helper.rb.

Our counter shows now only one record created in the database. Our let_it_be helper uses the transactional_tests feature from Ruby on Rails so we create one record once and it’s automatically removed after the test’s execution. You can configure the database_cleaner gem so that it uses transaction strategy between tests and truncate before running the suite. If you use transaction strategy for cleaning it won’t work with Capybara as it’s running as a separate process. Even with the blank examples, our test is faster by 60% because we are creating only one record in the database instead of 27.

2. Decrease the requests to external services

We have a Ruby on Rails app where we heavily rely on Rollbar - a tool that helps in error tracking, very useful for our Ruby devs. We came to a point where the app required a 500,000 calls plan, which sounds like a bit of overkill for the traffic we have on that app, and decided to see if we could save money here.

Our Ruby on Rails devs came up with a solution to decrease the count of requests to the monitoring service, which helped us decrease our calls plan in Rollbar. We fixed common errors, ignored failures caused by internet bots (mostly they are causing ActionController::RoutingError errors by trying to access non-existing endpoints), and reduced the amount of warnings, as we really didn’t need most of them. After implementing just these small changes in the Ruby on Rails app, we now manage to fit in under the 100,000 calls plan.

In another Ruby on Rails app, we use Papertrail as a log management service on daily basis. We use the logs for audits, as we integrate heavily with a dozen external systems, and we came to a point where our RoR app required the 50GB plan. We managed to reduce the log size by truncating logs and removing unimportant log messages - such as descriptions or encrypted attachments from incoming emails. Thanks to these changes, we were able to switch to a 16GB plan and significantly decrease the costs. On top of that, the performance of the Ruby on Rails app is much better now.

Those small steps do not sound like big savings on their own, but if you combine all costs that your app generates on external services, you can save a nice sum of money by auditing your application’s code and its integration with external services.

Think that your web applications development processes need streamlining? We can help with that. Ask iRonin about how we can help you to improve performance of your Ruby on Rails or Node.js apps, and save you money!