Mastering Ruby Concurrency: Fibers, Ractors & Choosing the Right Concurrency Model (Part 2)

Understand Ruby's concurrency models, including threads, fibers, and ractors, and learn how to choose the right approach for scalable applications.

Saikat_Ruby on Rails Expert

Saikat Kumar Dey

Technical Consultant

Welcome Back!

In Part 1, we explored how Threads enable concurrency in Ruby, how the Global VM Lock (GVL) affects ruby thread execution, and why Mutex is essential for protecting shared data.

Ruby threads are a great fit for most Rails applications. But they're not the answer to every concurrency problem.

What if your application needs to handle 100,000 waiting users?

Or what if it needs to fully utilize multiple CPU cores for computationally intensive work?

That's where fibers and ractors come in.

In this article, we'll explore how they work, when to use them, and how to choose the right concurrency model for your Ruby applications.

Why Ruby Threads Aren't Always Enough

Let's revisit our chat application example.

Imagine your server has 100,000 connected users.

Although all 100,000 users are online, only a small percentage are actively sending or receiving messages. Most users are simply waiting for the next message or notification.

Image-1.png

Even when ruby thread is idle, it still consumes memory and operating system resources. As the number of threads increases, so does the overhead of managing them.

In this scenario, thousands of threads spend most of their time doing nothing except waiting.

Wouldn't it be better if a small number of ruby threads could efficiently manage thousands of waiting tasks?

That's exactly the problem ruby on rails fibers were designed to solve.

Instead of creating one thread for every task, Ruby allows a single thread to manage many lightweight fibers, making applications far more memory-efficient while still handling a large number of concurrent waiting operations.

Meet Fibers

A fiber is the lightest concurrency primitive available in Ruby.

If Ruby threads are like hiring multiple employees to work on different tasks, fibers are like giving a single employee a well-organized to-do list.

Imagine you're working on several tasks at the same time.

Image-2.png

You didn't need another person to do those tasks—you simply switched to another piece of work while the first one was waiting.

Fibers work in a very similar way.

When one fiber reaches a point where it has to wait, Ruby can pause that fiber and continue executing another fiber that's ready to run.

Image-3.png

Because fibers are managed entirely by Ruby instead of the operating system, they're extremely lightweight. A single Thread can efficiently manage thousands of fibers while consuming far less memory than creating thousands of Threads.

This makes ruby fibers an excellent choice for applications that spend most of their time waiting, such as chat applications, WebSocket servers, and streaming services.

But how does Ruby know when to pause one fiber and continue another?

Let's see that in action.

How Fibers Work

Unlike Threads, fibers don't run automatically.

A fiber starts executing only when you explicitly tell it to, and it pauses only when it chooses to.

Let's look at a simple example.

Image-4.png

Output

Image-5.png

Here's what happened.

When we call fiber.resume, Ruby starts executing the fiber.

The fiber continues running until it reaches Fiber.yield.

At that point, it voluntarily pauses itself and returns control to the main program.

While the fiber is paused, the main program continues executing.

Later, when we call fiber.resume again, Ruby resumes the fiber from exactly where it paused—not from the beginning.

Image-6.png

In our example, we manually resumed the fiber.

In real applications, however, this switching happens automatically with the help of the fiber scheduler.

The Fiber Scheduler

Our previous example used fiber.resume manually to switch between fibers.

In a real application, that's not practical. Imagine managing thousands of fibers yourself!

This is where the fiber scheduler comes in.

Here is one real world example

Image-7.png

Introduced in Ruby 3, the fiber Scheduler automatically manages fibers whenever they perform a blocking operation.

Image-8.png

The best part is that your code still looks like normal synchronous Ruby code. You don't have to manually pause or resume every fiber—the fiber scheduler handles it for you behind the scenes.

This is what makes fibers a powerful choice for applications that handle thousands of concurrent I/O operations while using very little memory.

When Should You Use Fibers?

Fibers are designed for applications that spend most of their time waiting rather than performing calculations.

They're especially useful when you need to manage a large number of concurrent I/O operations while keeping memory usage low.

Some common use cases include:

  • Real-time chat applications
  • WebSocket servers
  • Live notifications
  • Streaming services
  • High-concurrency API clients

Imagine you're building a stock market application where thousands of users are connected through WebSockets.

Image-9.png

Creating a Thread for every connection would consume a significant amount of memory.

Instead, a small number of Threads can efficiently manage thousands of fibers, allowing your application to scale while using far fewer resources.

However, it's important to remember that fibers don't make your Ruby code execute faster.

They simply help your application use waiting time more efficiently.

If your application spends most of its time performing calculations instead of waiting, fibers won't provide much benefit.

So what should we use for CPU-intensive tasks?

That's exactly where Ractors come into the picture.

The Next Challenge: CPU-Intensive Work

So far, we've focused on applications that spend most of their time waiting.

But not every application is I/O-bound.

Imagine you're building a photo editing service.

Every uploaded image needs to be:

  • Resized
  • Compressed
  • Watermarked
  • Converted into WebP format

These tasks don't spend time waiting for a database or an API.

Instead, they're continuously performing calculations.

This is known as a CPU-bound workload.

Image-10.png

Unfortunately, ruby threads and fibers can't fully utilize all CPU cores in MRI Ruby because they're both restricted by the Global VM Lock (GVL).

Even if you create multiple ruby threads or thousands of fibers, only one can execute Ruby code at a time within a Ruby process.

To solve this problem, Ruby introduced ractors.

Unlike ruby threads and fibers, ractors are designed for true parallel execution, allowing Ruby code to run simultaneously on multiple CPU cores.

Let's see how they work.

Meet Ractors

The biggest difference between ruby threads and ractors is how they handle data.

Ruby threads share the same memory.

That's why multiple Threads can access the same variable, making synchronization necessary when working with shared data.

Ractors take a different approach.

Every ractor has its own isolated memory space.

Think of it like a team of developers.

Image-11.png

Ractors work in exactly the same way.

Each ractor owns its own data and communicates with other ractors by passing messages instead of sharing variables.

Let's create our first ractor.

Image-12.png

Output

Image-13.png

The code inside the ractor executes independently.

When the work is complete, the take method waits for the ractor to finish and returns its result.

This isolation is what makes ractors safe for parallel execution and allows multiple CPU cores to work simultaneously without interfering with each other.

Let's see a real-world example where ractors truly shine.

Ractors in Action

Let's put ractors to work.

Think of one example: A user uploads four photos for enhancement.

Image-14.png

Suppose your application needs to perform two computationally intensive tasks, such as checking whether two very large numbers are prime.

Since these calculations are completely independent, they can run simultaneously.

Image-15.png

Behind the scenes:

Image-16.png

Now compare that with Threads.

ChatGPT Image Jul 20, 2026, 11_21_10 AM.png

Although multiple ruby threads exist, only one can execute Ruby code at a time in MRI Ruby because they share the same GVL.

This is why Ractors are the preferred choice for CPU-intensive workloads such as image processing, video encoding, scientific simulations, and large-scale data analysis.

Now that we've explored ruby threads, fibers, and ractors individually, let's answer the most important question:

Which one should you use?

Choosing the Right Concurrency Model

After exploring threads, fibers, and ractors, one thing should be clear:

There isn't a single "best" concurrency model.

Each one is designed to solve a different problem.

A simple way to choose is to ask one question:

Image-18.png

Easy way to remember:

Image-19.png

Here's a quick summary:

Threads

Best for typical I/O-bound applications such as Rails web requests, database queries, API calls, file operations, and background jobs.

Fibers

Best for applications that need to manage thousands of concurrent waiting tasks while keeping memory usage low, such as WebSocket servers, chat applications, and streaming services.

Ractors

Best for CPU-intensive workloads that need true parallel execution, such as image processing, video encoding, data analytics, and scientific computing.

What We've Learned

In this two-part series, we've explored Ruby's complete concurrency toolbox.

  • Threads improve concurrency for I/O-bound applications.
  • Fibers improve scalability by efficiently managing thousands of lightweight waiting tasks.
  • Ractors unlock true parallel execution for CPU-intensive workloads.

The most important lesson isn't learning every API—it's understanding the problem you're trying to solve.

Choose the right concurrency model, and Ruby gives you the tools to build applications that are faster, more scalable, and easier to maintain.

If you need help with your Ruby on Rails application or you are looking to hire experienced Ruby on Rails developers for your current project, get in touch with our team at RailsFactory.

We're here to help you build, improve, and scale your Rails application.

Written by Saikat Kumar Dey

Saikat Kumar Dey is a Technical Consultant at RailsFactory, passionate about building scalable, high-performance web applications with Ruby on Rails. He thrives on exploring new technologies and innovative solutions.

Tags

Other blogs

You may also like


Your one-stop shop for expert RoR services

join 250+ companies achieving top-notch RoR development without increasing your workforce.