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:
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:
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:
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.
- 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:
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!