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!