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.
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.
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.
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
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.
Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023Coding
Posted on 07 Apr, 2023