Improve the performance of your Rails application through load testing

One of the top priorities for any application is to ensure that it performs well under various load scenarios. This article aims to illustrate the importance of load testing in a Ruby on Rails application and provide a comprehensive guide to conducting one.

Why load testing?

Whether your Rails application serves a few or millions of users, load testing is a critical aspect of the development cycle. The primary goals of load testing are to collect metrics and improve performance.

Determine the maximum capabilities of your application

Ensure that the application effectively utilizes server capacity. Remember, load testing is designed to simulate peak traffic scenarios to identify and resolve potential bottlenecks before they become a problem.

Setting up a load test

The process starts with selecting a machine that isn't being used by the application. Keeping this machine close to your application servers helps to ensure accurate results by minimizing network latency.

There are many tools available for performing load tests. For simple tests, ApacheBench and Siege are reliable tools. They can be installed using brew:

brew install ab siege oha

To test a single page, all you need to do is specify the number of requests, the concurrency (parallel requests), and the URL. Here's how to run it using ApacheBench:

ab -n 10000 -c 100 https://myapp.com/one

We will send 100 parallel requests up to a total of 10k. The critical data points to monitor here are failed requests and requests per second.
For load tests involving multiple URLs, Siege is a perfect choice. It allows you to create a file with all the URLs you want to test:

siege -t 1 -c 100 -f my_urls.txt

Here availability should be 100% and transaction_rate is the primary metric to look at, which is our requests per second.

The next cool tool is oha. We can use it similar to the ab - but it shows results much nicer.

oha -c5 -z 5s -m GET --latency-correction --disable-keepalive --redirect 0 http://localhost:3000/api/v1/me

This command is saying: start a 5-second test where you send GET requests to http://localhost:3000/api/v1/me, using five connections, turning off HTTP keepalive functionality, not following any redirects, and correcting for network latency.

More complex scenarios

If you want to automate this more you can use tools like typhoeus. On my side, I'm mostly writing rake tasks that use the gem, which can prepare a real-life scenario with for example sorting or searching data.

hydra = Typhoeus::Hydra.new
100.times do
  request = Typhoeus::Request.new("www.example.com", followlocation: true)
  request.on_complete do |response|
    #do_something_with response
  end
  hydra.queue(request)
end
hydra.run

Measuring performance

Beyond running the test, analyzing the data is critical. An application performance monitoring (APM) tool will help you interpret the data and identify bottlenecks. Monitoring server metrics such as CPU usage, load average, and memory, as well as application-level statistics, provides a comprehensive understanding of the application's performance.

Optimization

Once you have gained insight from your load-testing data, the next step is optimization. Whether it's fully utilizing your server's CPU or optimizing your code for more requests per second, your focus should be on getting the most out of your resources.

In conclusion

Load testing is not just a routine task - it is a prerequisite for performance improvement. By following the steps and guidelines outlined in this article, you can effectively load-test your Rails application, interpret the data for actionable insights, and optimize your application for superior performance.

Happy testing!