Rails Performance Unveiled: Identifying Common Culprits
When it comes to improving application performance and areas to focus on, I would recommend looking at the APM data, and then deciding which areas to prioritize.
However this article isn’t about where to focus efforts, but rather a compilation of techniques to improve your application’s performance, from tackling common problems like N+1 queries and database indexing to leveraging the jemalloc
memory allocator. Let’s look at these performance-boosting strategies designed to fine-tune your application.
Different strategies to improve the performance of the application
N+1 queries
One of the most common problems I see when working on improving the performance of Rails applications is the presence of an N+1 query. Most times they are not hard to solve. However, it can be hard to track down what code is triggering the N+1 queries. Sometimes they are wrapped up in the view files, and some in the before_action
functions. The impact of the N+1 query depends on how big the tables are that are involved in the query, and how many times that query is called.
Missing db indexes / Wrong indexes
Another area prone to performance bottlenecks lies within the database. It is quite common to not think about what indexes the table should have when one is creating the model and writing its migration. But as time goes on and the database tables keep getting larger and the queries get complex, one of the most common problems is that the queries take a long time to return a result from the database. And once that happens, one of the common reasons is that the queries are not using any sort of indexing to make the fetching of data faster. And if indexes do exist, they might not be applied to the fields of interest. You might be querying on a field called state in the users table but you have indexed the table on a column other than state. This can also lead to slower queries.
Increasing memory usage
Escalation in memory usage stands out as a prevalent challenge in Ruby on Rails applications, where the increase can be sudden or gradual. Finding the root cause of the memory issues could be one of the hardest things to debug.
A solution worth trying, before you dig any further into understanding the reason for increased memory usage, is to switch to the Jemalloc memory allocator . In a lot of instances, it has been reported that the memory usage was reduced by up to 50% just by changing the memory allocator from Malloc to Jemalloc. But if you do end up applying this, we would recommend always observing the memory usage pattern for a few days. A thing worth noting is that Jemalloc will solve the memory bloat problem, but will not solve the memory leak problem you might be facing. For memory leaks, you will have to find out where the leak is coming from in the code and then fix the code.
Loading all JS
and CSS
synchronously
Sometimes you can have a very fast backend service and APIs, but if the pages take too long to load on the browser, the user would not care if the backend is fast. One of the common reasons for high page load times is loading all the JavaScript synchronously when it can be loaded asynchronously. There are 2 options, async
and defer
that can be used depending on different scenarios and it can have an impact on how fast the page loads.
Unused JS
and CSS
As we continue to add new features and remove some from our product, sometimes there is JavaScript and CSS that is not needed for any of the existing features, and yet the unused JS and CSS just stay in our code base forever causing unnecessary code bloat. The problem is that the more of such code that is not needed anymore we have, the larger the size of the JS and CSS files are, which leads to higher load times for these assets and more time needed to execute them. It is very easy to keep on adding new JS and CSS without touching the old ones, but removing the code that is not needed anymore has its advantages and can help make the app faster.
Underprovisioned servers
A common yet overlooked factor contributing to sub-optimal Rails performance is the use of underprovisioned servers. An underprovisioned server may not be able to handle increasing traffic on your application, suffer from ever increasing memory usage, server restarts, slow response on database queries, and impacting overall user experience. APMs can help in identifying the problem and also act as a feedback loop when the servers are upgraded.
Conclusion
While this post barely scratches the surface of these techniques, I hope that it gives you ideas on what optimizations to apply to your application. Need help improving your application’s performance? Check out Tune Report and send us a message today! .