Concurrency in Rails


Created by Stan on 07-04-2023


Concurrency is a crucial aspect of modern web applications, allowing multiple tasks to be executed simultaneously, thereby improving performance and responsiveness. In this blog, we will discuss concurrency in Rails and how to implement it in a bookstore application using code samples.

Understanding Concurrency in Rails

Rails is built on top of the Ruby programming language, which uses the Global Interpreter Lock (GIL) to manage thread safety. This means that, by default, Ruby can only execute one thread at a time. However, Rails can still leverage concurrency through multi-threading, multi-processing, and asynchronous processing.

Multi-threading with Puma

Rails applications typically use the Puma web server, which supports multi-threading. With Puma, you can configure the number of worker processes and threads per worker in the config/puma.rb file.

workers 2
threads_count = 5
threads threads_count, threads_count

In this example, we have configured Puma to use two worker processes, each with five threads. This allows 10 concurrent requests to be processed.

Multi-processing with Sidekiq

When dealing with long-running tasks, such as sending emails or processing images, it's beneficial to offload the work to background jobs using a multi-processing library like Sidekiq.

First, add Sidekiq to your Gemfile and run bundle install:

gem 'sidekiq'

Next, create a background job for sending order confirmation emails:

rails generate job SendOrderConfirmationEmail

Now, modify the generated job:

# app/jobs/send_order_confirmation_email_job.rb
class SendOrderConfirmationEmailJob < ApplicationJob
  queue_as :default

  def perform(order_id)
    order = Order.find(order_id)
    OrderMailer.order_confirmation(order).deliver_now
  end
end

In your controller, you can now use the background job to send emails asynchronously:

# app/controllers/orders_controller.rb
def create
  @order = Order.new(order_params)
  if @order.save
    SendOrderConfirmationEmailJob.perform_later(@order.id)
    redirect_to @order, notice: 'Order was successfully created.'
  else
    render :new
  end
end

Asynchronous Processing with Active Job

Active Job is a framework in Rails that provides a unified interface for working with various background job processors, such as Sidekiq, Resque, or Delayed Job. It allows you to easily switch between job processors without changing the application code.

Here's an example of using Active Job to create a job for updating book inventory:

rails generate job UpdateBookInventory

Modify the generated job:

# app/jobs/update_book_inventory_job.rb
class UpdateBookInventoryJob < ApplicationJob
  queue_as :default

  def perform(book_id, quantity)
    book = Book.find(book_id)
    book.with_lock do
      book.update!(inventory: book.inventory - quantity)
    end
  end
end

In your controller, you can now use the background job to update book inventory:

# app/controllers/orders_controller.rb
def create
  @order = Order.new(order_params)
  if @order.save
    @order.line_items.each do |line_item|
      UpdateBookInventoryJob.perform_later(line_item.book_id, line_item.quantity)
    end
    redirect_to @order, notice: 'Order was successfully created.'
  else
    render :new
  end
end

Concurrency is essential for building scalable and high-performing web applications. In this blog, we discussed different approaches to implementing concurrency in Rails, such as multi-threading with Puma, multi-processing with Sidekiq, and asynchronous processing with Active Job. By using these techniques, you can optimize your Rails applications, like our bookstore example, to handle multiple tasks simultaneously and provide a better user experience.

Keep in mind that when working with concurrent systems, it's essential to consider thread safety and potential race conditions. Rails provides mechanisms, such as the with_lock method, to help ensure data integrity when updating shared resources.

As you continue to build and scale your Rails applications, be sure to explore and leverage these concurrency options to maximize performance and responsiveness.



Related Posts