ruby on rails, concurrency, web development, performance optimization, backend development,

Supercharging Ruby on Rails with Concurrent Threads

Sebastian Schkudlara Sebastian Schkudlara Follow May 21, 2024 · 3 mins read
Supercharging Ruby on Rails with Concurrent Threads
Share this

Supercharging Ruby on Rails with Concurrent Threads

Optimizing performance is crucial in web development, especially with complex AI-driven services. Ruby on Rails traditionally handles tasks synchronously, but some scenarios benefit from concurrent processing. Threads enable parallel task execution, reducing latency.

Why Use Threads in Rails?

Threads allow parallel execution within a single process, improving responsiveness and throughput. This is beneficial for:

  • Concurrent API calls
  • Processing multiple data streams
  • Handling background jobs

Why concurrent-ruby?

While Ruby supports threads, concurrent-ruby offers:

  • Higher-Level Abstractions: Simplifies task management with thread pools, futures, promises, and actors.
  • Thread Safety: Provides thread-safe collections to avoid race conditions.
  • Ease of Use: Intuitive API reduces boilerplate code.
  • Better Error Handling: Robust mechanisms for handling errors.
  • Resource Management: Efficiently manages system resources.

Example: Parallel Task Execution

In my multi-agent application, I use concurrent-ruby for concurrent tasks. Here’s a snippet from my ButlerAiService:

require 'concurrent'

module Handlers
  class ButlerAiService
    def call
      return default_hash if @prompt.blank?

      responses = Concurrent::Hash.new
      thread_pool = Concurrent::FixedThreadPool.new(10)

      %i[task_engine chat_history other].each do |layer|
        thread_pool.post do
          begin
            responses[layer] = Layers.const_get("#{layer.to_s.camelcase}Layer").new(@options).call
          rescue => e
            Rails.logger.error("Error in #{layer}: #{e.message}")
          end
        end
      end

      thread_pool.post do
        begin
          responses[:embeddings_response] = Layers::SearchEmbeddingLayer.new(@options).call
        rescue => e
          Rails.logger.error("Error in embeddings_response: #{e.message}")
        end
      end

      thread_pool.shutdown
      thread_pool.wait_for_termination

      responses
    end
  end
end

Key Concepts

  1. Thread Pool: Reuse pre-initialized threads for multiple tasks.

     thread_pool = Concurrent::FixedThreadPool.new(10)
    
  2. Posting Tasks: Run tasks concurrently in separate threads.

     %i[task_engine chat_history language model audio_response scraping image_response video_response music_response].each do |layer|
       thread_pool.post do
         responses[layer] = Layers.const_get("#{layer.to_s.camelcase}Layer").new(@options).call
       end
     end
    
  3. Shutdown and Wait for Termination: Ensure all tasks complete before proceeding.

     thread_pool.shutdown
     thread_pool.wait_for_termination
    

Pros and Cons of Using Threads

Pros:

  • Improved Performance: Reduces processing time.
  • Efficient Resource Utilization: Runs multiple tasks concurrently.
  • Scalability: Easier to scale services.

Cons:

  • Complexity: Introduces complexity in thread management.
  • Debugging: Challenging in a multi-threaded environment.
  • Resource Contention: Potential bottlenecks if not managed properly.

Conclusion

Using concurrent-ruby for managing threads in Ruby on Rails offers robust tools for concurrency, making it easier to write, manage, and debug code. This approach significantly boosts performance by enabling parallel task execution, making applications more responsive and scalable.

In my multi-agent application, concurrent-ruby has improved performance and resource utilization. If you’re looking to optimize your Rails application, give concurrent-ruby a try. The performance gains might surprise you!

Happy coding!

Sebastian Schkudlara
Written by Sebastian Schkudlara Follow
Hi, I am Sebastian Schkudlara, the author of Jevvellabs. I hope you enjoy my blog!