Application Performance
Rails Performance Tips
Summarized using AI

Rails Performance Tips

by Richard Huang

In his talk at RubyConf AU 2016, Richard Huang addresses the prevalent issue of performance optimization in Ruby on Rails applications. He emphasizes that many developers overlook performance tuning, despite Rails applications often being perceived as slow. The presentation aims to provide strategies for monitoring performance, identifying bottlenecks, and implementing effective fixes to enhance Rails apps.

Key Points Discussed:
- Frontend Optimization:
- Focus on minimizing HTTP requests by concatenating CSS and JS files.
- Utilize Gzip compression for CSS and JS to significantly reduce file sizes.
- Avoid unnecessary cookies in requests by serving assets from a different domain and leverage Content Delivery Networks (CDNs) for speed.
- Position JavaScript at the end of the HTML to improve page rendering times.

  • Backend Optimization:

    • Importance of backend performance monitoring, especially during traffic spikes.
    • Utilize performance monitoring tools such as New Relic or Skylight to discover bottlenecks and analyze response times for web transactions.
    • Reproduce performance issues in a local environment to test fixes effectively.
    • Address common backend issues like the N+1 query by implementing eager loading with Rails’ 'includes' method, and create counter caches to improve query efficiency.
  • Performance Improvement Practices:

    • Execute batch operations like bulk inserts and updates to decrease the number of SQL transactions.
    • Use caching strategies to maintain performance while reducing database hits, including Rails fragment caching.
    • Opt for faster JSON parsing libraries to enhance API response times.
    • Implement memory optimization techniques to manage object allocations better and reduce garbage collection frequency.

Conclusion:
Richard Huang stresses the importance of a comprehensive approach to both frontend and backend optimizations. Continuous monitoring, effective reproduction of issues, and robust benchmarking for performance testing are essential processes in maintaining healthy Rails applications. Developers are encouraged to apply the discussed tips and tools to sustain and improve application performance effectively. The demo code is available on GitHub for further exploration and practical understanding of these performance insights.

00:00:00.640 Hello everyone, this is not my first time speaking, but it is my first time in Australia talking about Rails performance tips. I hope you enjoy it.
00:00:09.880 When you launch a website, you may feel it is slow on the browser, and you might wonder where to start optimizing it. Steve Souders, who wrote 'High-Performance Websites', mentioned that 80 to 90 percent of the end-user response time is spent on the front end. Therefore, it is better to focus on optimizing your frontend performance first.
00:00:26.880 Three years ago at the Ruby Conference in Australia, Keynote speaker Mario gave an excellent presentation on frontend performance optimization. I don't want to repeat everything they said, but I’ll quickly list their tips, categorizing them for clarity. The first tip is to send as few requests as possible. When a user visits a website, the browser sends a request to retrieve the HTML and then multiple requests for CSS and JavaScript files. Sending HTTP requests is expensive, so by default, Rails concatenates CSS and JavaScript files into one. After the HTML is served, it will only send one request for CSS and one for JavaScript files. Additionally, you can use CSS sprites if you have many small images or icons; it reduces the number of HTTP requests.
00:01:06.760 The second tip is to return as much payload as possible. By default, Rails will minify or compress your CSS and JS files using Gzip to reduce their size. Generally, your CSS and JS files can be compressed by 80 to 90 percent. For images, there are tools available to reduce the size without losing any quality. Next, consider the cookies in your requests for CSS and JavaScript. You likely see cookies in the request body that are not needed, so you can serve your CSS and JavaScript from another domain to avoid sending these cookies in HTTP requests, which will speed up loading time. The third tip is to use the fastest resources possible. Utilizing a Content Delivery Network (CDN) can ensure that resources are geographically closer to your users, leading to faster response times.
00:02:06.120 Other tips include placing JavaScript at the bottom of your body content to allow the page to render faster. Now, besides frontend performance tips, I’d like to discuss backend performance tips. You might think that backend performance is less important because it only accounts for 10 to 20 percent of the total response time. However, this is not true, as during traffic spikes, backend response times can play a much bigger role. If you see a backend performance issue, how should you address it? The first and most important rule is: don't guess. Avoid looking at the code and making assumptions about performance problems. Instead, find the real bottleneck by using a performance monitoring tool. I prefer using New Relic, but you also have options like Skylight.
00:03:24.000 These tools allow you to see web transaction average response times on your app server or browser. You can analyze the time spent on middleware, Ruby, your database, and more. In New Relic, you can view transactions ordered by response time, which helps you identify and improve the slowest transactions. For each transaction, you’ll receive a breakdown of segments displaying the name, time percentage, average cost, and average time for each segment. This information assists in discovering performance bottlenecks. Once you identify a bottleneck, you need to reproduce it locally. Simulate the production environment and install the necessary dependencies such as Postgres, Redis, or Sidekiq. You should also replicate production data because this is essential to accurately reproduce the user load under production conditions. While running locally, always modify performance by using New Relic's developer mode, or choose other tools like Rack Mini Profiler to generate performance reports. If you obtain similar performance results locally, you have successfully replicated the performance issue in your environment.
00:04:58.640 It is often challenging to prepare data locally, especially in cases where there is a large amount of data in production or where the performance issue only affects specific users. A possible tip is to create a development instance on production by running the application in a development environment while still connected to the production database and cache. This way, you can have a similar user load and dataset as in the production environment. You can then send specific requests to the development instance to gather detailed performance reports for those particular requests and users, allowing you to address the identified performance issues locally. After changing the code to rectify the performance issue, ensure it indeed speeds up the application by conducting benchmark tests. The Rails Perf gem allows you to write test code for performance testing.
00:06:42.760 For example, you could send a request to your homepage, and after running rake test:benchmark, it will display the total process time, memory usage, and objects allocated. It's crucial to perform benchmarking both before and after fixing the performance issue to compare the results and verify that you have indeed improved the application's performance. Consider using Apache Benchmark (ab) or Siege to make concurrent requests to your local instance and monitor the average response times. Finally, let me summarize the key points: first, you should continuously monitor production performance to identify bottlenecks. Next, reproduce them locally, fix the issues, benchmark to confirm improvements, and deploy the changes back into production, keeping an eye on ongoing performance.
00:08:10.720 Now, I will share some backend performance tips I typically use, categorized for convenience. The first is still to send as few requests as possible. An example is the N+1 query issue, where loading 10 comments for each comment requires an additional SQL request to fetch user data from the database for each comment. This can lead to significant performance degradation.
00:08:47.439 To address this, you can modify your Rails 'includes' method to eager-load the user data, allowing you to fetch all user data with a single SQL query instead of multiple. Now, the user find operation only needs to be called once, which significantly reduces response times. Additionally, I created a gem called Bullet to help highlight N+1 query problems during development. Another relevant tip is about the counter cache. If you load 10 posts and, for each post, calculate the size of the comments through separate SQL queries, it can again lead to multiple slow queries. A better solution is to add a counter cache column to the posts table, maintaining a pre-calculated comment count for each post. This modification reduces the number of SQL queries significantly and optimizes performance.
00:10:26.759 The Bullet gem can also issue warnings during development to inform you of necessary column additions for caching. An additional gem, Counter Culture, can enhance your counter caching capabilities significantly. In situations requiring more complex calculations, such as determining the average rating for approved comments on posts, be aware that executing average SQL queries for each post is inefficient. You can use eager loading to calculate these averages in a single query, which can greatly improve the performance of your application.
00:12:04.920 If you are using PostgreSQL and don't need real-time aggregated results, consider using materialized views. They pre-calculate aggregation values, speeding up access when fetching data. Another performance issue is related to multiple inserts. Each insert can involve multiple SQL transactions, resulting in degraded performance. Rather than creating posts one at a time, you can utilize ActiveRecord's bulk insert capabilities to insert multiple records in a single operation.
00:14:27.000 This approach reduces the number of database transactions and improves overall response time significantly. Similar principles apply to updates; use 'update_all' for batch updates, and for deletions, rely on 'destroy_all' or 'delete_all' for effective batch operations. Returning smaller payloads is essential and can also enhance performance. If you only need to display the titles of posts, ensure you query only the title attribute instead of fetching all attributes, resulting in faster response times.
00:15:00.160 When involving caching, ensure you use the Rails caching mechanism wisely. For example, implement fragment caching effectively to reduce database queries and speed up rendering times. While using cache is pivotal, be cautious about multiple queries coming from the cache as they can still distort performance. Instead, prioritizing Rails' caching solutions can help streamline the process by fetching multiple data points at once.
00:16:27.680 You can improve the performance of JSON rendering in APIs by switching to faster JSON parsing gems such as Oj or Oj mimics Json, leading to quicker API response times. Memory optimization is crucial too; developers often find their Ruby on Rails applications consuming excessive memory, leading to frequent garbage collection. Techniques for reducing object allocations include querying batches of records instead of loading everything at once.
00:17:45.280 For example, instead of loading dozens of records at a time, query 1,000 records, process them, and release the allocated objects before repeating the process. While this requires multiple queries, it effectively reduces memory usage and garbage collection frequency.
00:18:56.840 To summarize, frontend performance optimization is important, but don't neglect backend performance tuning. Always aim to minimize requests, return smaller payloads, and utilize the fastest resources available. In production, monitor your application's performance closely, identify and address bottlenecks effectively, and continuously assess improvements using benchmarking tools before deploying to live environments.
00:21:17.760 The demo code I've mentioned is available on GitHub; check it out and run it locally to explore performance insights with the New Relic demo. Thank you for your attention!
Explore all talks recorded at RubyConf AU 2016
+11