To Thread or Not to Thread: An In-Depth Look at Ruby’s Execution Models

An in-depth look at threads vs processes in Ruby web applications, and when you should use each.

Jean Boussier
16 min readadvanced
--
View Original

Overview

The article examines the growing trend of using threaded servers for deploying Ruby applications, particularly focusing on the advantages of increased throughput and memory efficiency. It discusses the mechanics of memory usage in web applications, the implications of Copy on Write (CoW), and the trade-offs between using threads and processes.

What You'll Learn

1

How to evaluate memory usage in Ruby applications using threads and processes

2

Why Copy on Write efficiency is crucial for optimizing Ruby server performance

3

When to choose between threaded and process-based servers for Ruby applications

Prerequisites & Requirements

  • Understanding of Ruby application deployment and server architecture

Key Questions Answered

What are the main benefits of using threaded servers for Ruby applications?
Threaded servers like Puma are preferred for Ruby applications because they enhance throughput without significantly increasing memory usage. This efficiency is particularly beneficial in environments with limited resources, allowing more concurrent requests to be handled effectively.
How does Copy on Write affect memory usage in Ruby applications?
Copy on Write (CoW) allows processes to share memory until a modification is made, which can significantly reduce memory overhead. This technique is particularly useful in Ruby, where forking processes can otherwise lead to high memory consumption if not managed properly.
Why is Resident Set Size (RSS) not a reliable metric for memory usage in Ruby?
RSS can be misleading because it reports the total memory usage of processes without accounting for shared memory. When processes share memory regions, RSS can inaccurately reflect higher memory usage, making it less useful for assessing actual memory consumption.
What strategies can improve Copy on Write efficiency in Ruby applications?
To enhance CoW efficiency, developers should preload applications to maximize shared memory usage and avoid lazy-loaded variables that prevent memory sharing. Techniques like using constants instead of memoized variables can also help maintain performance.

Key Statistics & Figures

Memory usage with two single-threaded processes
700MiB
This is compared to a single process with two threads, which uses only 500MiB, demonstrating the memory efficiency of threaded servers.
Percentage of static memory shared in an application
65.66%
This statistic indicates that two-thirds of the static memory in the application is shared, highlighting the effectiveness of CoW.

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

Backend
Ruby
Used for building web applications and managing server processes.
Backend
Puma
A threaded web server commonly used for deploying Ruby applications.
Backend
Unicorn
A process-based server that provides reliable request handling for Ruby applications.
Backend
Sidekiq
A job processor that utilizes threads for handling background tasks in Ruby applications.

Key Actionable Insights

1
Enable application preloading to maximize Copy on Write efficiency.
Preloading allows shared memory to be utilized effectively, reducing overall memory usage and improving performance in Ruby applications.
2
Consider using process-based servers like Unicorn for better request timeout handling.
Process-based servers can cleanly interrupt long-running requests, enhancing application resiliency compared to threaded servers where interruptions can lead to resource contention.
3
Use Proportional Set Size (PSS) for a more accurate measure of memory usage.
PSS accounts for shared memory by dividing the size of shared regions by the number of processes, providing a clearer picture of actual memory consumption in Ruby applications.

Common Pitfalls

1
Relying solely on Resident Set Size (RSS) for memory metrics can lead to misinterpretations of memory usage.
Because RSS does not account for shared memory, it can falsely indicate higher memory consumption, leading to inefficient resource allocation decisions.

Related Concepts

Memory Management In Ruby Applications
Concurrency Models In Ruby
Performance Optimization Techniques For Web Servers