Performance and Optimization in Ruby on Rails
Tags: ruby, rails, webdev, programming
Published: 2024-02-26 16:57:44

<p>
Performance and optimization in Ruby on Rails applications are crucial topics to ensure a fast and efficient experience for users. This involves several techniques, from caching to configuration and scalability adjustments.
</p>

<h2>Caching</h2>

<p>
Stores the HTML generated from pages in cache to serve quickly to subsequent users, avoiding the need to process the request again.
Obviously this is super fast. Unfortunately, it cannot be applied to all situations (such as pages that need authentication) and since the web server is literally just serving a file from the file system, cache expiration is an issue that needs to be resolved.
</p>

<p>
To enable page caching, you need to use the "caches_page" method.
</p>

```ruby
class ProductsController < ActionController
 
  caches_page :index
 
  def index
    @products = Products.all
  end
end
```

<h2>Fragment Caching</h2>

<p>
Caches specific parts of pages, such as chunks of HTML, to reduce rendering time.
For example, if you want to show all orders placed on your website in real time and you don't want to cache that part of the page, but you want to cache the part of the page that lists all available products, you could use this snippet of code:
</p>

```ruby
<% Order.find_recent.each do |o| %>
  <%= o.buyer.name %> bought <%= o.product.name %>
<% end %>
 
<% cache do %>
  All available products:
  <% Product.all.each do |p| %>
    <%= link_to p.name, product_url(p) %>
  <% end %>
<% end %>
```

<p>
The cache block in our example will be linked to the action that called it and will be written to the same location as the Action Cache, which means that if you want to cache multiple fragments per action, you must provide an "action_suffix" for the call. cache:
</p>

```ruby
% cache(:action => 'recent', :action_suffix => 'all_products') do %>
  All available products:
```
<p>
and you can expire using the "expire_fragment" method, like this:
</p>

```ruby
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products')
```

<p>
If you don't want the cache block to be tied to the action that called it, you can also use global-key shards by calling the cache method with a key, like so:
</p>

```ruby
<% cache('all_available_products') do %>
  All available products:
<% end %>
```

<p>
This fragment is then available to all actions in the ProductsController using the key and can expire in the same way:
</p>

```ruby
expire_fragment('all_available_products')
```

<h2>Scalability</h2>

<p>
SQL query optimization, using indexes and sharding to distribute data across multiple databases is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set instead of running the query against the database again. For example:
</p>

```ruby
class ProductsController < ActionController
 
  def index
    # Run a find query
    @products = Product.all
 
    ...
 
    # Run the same query again
    @products = Product.all
  end
 
end
```

<p>
The second time the same query is executed against the database, it will not hit the database. The first time the query result is returned, it is stored in the query cache (in memory) and the second time it is extracted from memory.
</p>

<h2>Application Servers</h2>
<p>
Use of load balancers and horizontal scaling to handle increased traffic.
</p>
<p>
Example: Configuring application servers such as Puma or Unicorn for load balancing.
</p>

<h2>Distributed Cache</h2>
<p>
Using distributed caching to share data across multiple servers.
</p>

<p>
Example: Using Redis as a shared cache between application instances.
</p>

```ruby
# Redis configuration for distributed cache in config/initializers/cache.rb
Rails.application.config.cache_store = :redis_store, {
  host: "localhost",
  port: 6379,
  db: 0,
  namespace: "app_cache"
}
```

<h2>Configuration Settings</h2>
<p>
Environment Settings: Specific adjustments for different environments (development, production, testing) to optimize performance at each phase of the application lifecycle.
</p>
<p>
Example: Configuring different databases for test and production environments.
</p>

<h2>Server Tuning</h2>
<p>
Web server and database configuration adjustments for better performance.
</p>
<p>
Example: Configuring the maximum number of simultaneous connections on the web server.
</p>

<h2>Conclusion</h2>
<p>
In summary, optimizing the performance of Ruby on Rails applications involves a combination of intelligent caching, efficient scalability, and fine-tuning configuration. These practices ensure that the application can handle large volumes of traffic quickly and responsively, providing a better experience for users.
</p>
<p>
These are just a few examples of how performance and optimization techniques can be applied in real-world scenarios in Ruby on Rails applications.
</p>