Test your Understanding of Ruby Concurrency
NOTE
Time travel — from 2026: today we have Ruby 4 with its Ractors, promising multi-core saturation with a single Ruby MRI process (a claim that deserves its own blog post).
But back in 2020, way before “Ractors” were a thing, I put together this little “self-test”, so you can find out how well you understand ruby concurrency model, and how it maps onto a real deployment architecture.
IMPORTANT
Watch the two presentations I did on the subject of Ruby Concurrency and Parallelism:
This post is for those who think they truly understand Ruby Concurrency well enough to plan a deployment for a very popular social network that runs of on Puma — a multi-threaded web server.
Parallelize This 👇🏻
The associated talk on Ruby Concurrency, was born out of hearing senior engineers discuss an out-of-place optimization of a business process, which demonstrated an obvious lack of understand of Ruby Concurrency, their limitations and trade-offs. And no wonder — concurrency is hard. In any language.
Ruby, actually, makes it pretty damn easy. And yet, just as it’s easy to write Thread.new { } it is just as easy to shoot yourself in the foot, foot, foo, ..t, fo, ooot ,…
Did you get my joke? I know, I know. Very clever. HA!
Without further ado, let’s get to it. Below you’ll find two concurrency questions with multiple choice answers. Your answers are not recorded, so don’t worry — try as many times as you like.Room and collect everyone’s opinion and document it in the following table
Problem [A]: “Getting Ruby to Utilize all Cores”
Let’s say we are running a very popular Rails website, and we purchased a “beefy” multi-core server with 8 cores and lots of RAM. Our site is very popular, and so we need to squeeze as much throughput from the server as possible.
We are trying to decide how to configure Puma on this server. In particular we are looking to agree on three most important numbers for Our Puma configuration.
- How many master puma processes to start (on different ports) in the case the socket is the point of contention (we’ll call this number
P) - How many workers to run per single master process (we’ll call this number
W) - How many threads should be in each worker (we’ll call this number
T)
So we are in the room arguing about this, and our esteemed recently hired Staff Engineer went out for lunch. So we are left to our own devices, and without an authority figure to help us decide based on their vast experience and deep knowledge of the UNIX sockets, concurrent processing, as well as Ruby.
God dammit, where is a Staff Engineer where you need one?
Anyway, we decide to go around the room and ask each of the technical member present their opinion. Surely it’ll work the democratic way since it clearly works in politics so well 😂
So we go around the Room. And ask every member their opinion documenting it in a following table
| Engineer/Role | Their Answer | Their Reasoning |
|---|---|---|
| Junior Engineer | P=1 W=1 T=8 | One master Puma is just fine, in fact one worker is just fine. That’s what threads are for, right? |
| DevOps Engineer | P=1 W=8 T=1 | She also thinks one master Puma is is just fine, and a worker per CPU makes sense, since she doesn’t know Ruby too well and can’t say whether threads will help or cause context switching contention that she saw in one of the troubleshooting sessions on Solaris. |
| Front-end Engineer | P=2 W=4 T=16 | Front-end engineer is concerned about socket contention. He heard that socket contention degrades performance and therefore he’s voting for at least two master Puma processes on different ports each with four workers and 16 threads |
| -------- | ------- | -------- |
Your job is to play the role of the Staff Engineer and decide, as well as explain the correct answer.
The Problem
TIP
NOTE that your answers are definitely not sent to NSA and the CIA, or recorded onto the the very large and secure tape, so when the government suddenly needs brilliant engineers to join the fight against the inevitable alien invasion, they know who to call. Definitely not 😂
So a reminder on variables we are working with:
| Variable | Description |
|---|---|
| P | Number of Puma master processes that bind to a socket |
| W | Number of workers each Puma master process spawns |
| T | Number of threads in each worker |
TIP
How to play: Choose an answer you believe is correct. If you selected the wrong answer, it will turn red, while the correct answer will be green. Press the button below the quiz to see the explanation and the reasoning. But, before you do that, you can try as many times as you like.
For each option, we also provide the number that’s a product of all of the variables. You may find it useful.
To optimally saturate this 8-core server with a Ruby Application that runs on MRI Ruby (C-Ruby), to drive server utilization as close to 80%-90%, without sacrificing latency, we should set
P,W, andTas follows:
Problem [B]: “Speeding up a Computation by Parallelization”

In this problem, we have a large queue (i.e. an Array) pre-filled with various objects, and we need to compute a SHA256 of each object using a SHA-function provided to us. Memory is not an issue.
We know a few things about these objects, and in addition we’ve been given some important constraints by our Operations team, and we must follow them:
NOTE
-
All objects in the array are independent of each other, and so the SHA can be computed in any order, on any object.
-
Computing SHA requiring no IO, just CPU.
-
We are allowed one and only one active/running Ruby process at all times.
-
We can choose any Ruby implementation for this task.
-
We still have access to the same 8 cores from the previous section, since we are still running on the same hypothetical server.
-
We can not modify the actual SHA routine — this is provided like a black box.
Well, we are unhappy with how long it takes to compute 10,000 SHA, and We would like to optimize this function and speed up the computation somehow.
Question
Will the overall operation be faster if we created 8 threads within the single Ruby process we are allowed to start, and then split the SHA256 computation across the eight running threads?
Answers
Conclusion
So, how did you do?
Hopefully, this was helpful to folks trying to tune Sidekiq and Puma for their multi-threaded environments.
Have any questions? Suggestions? Please leave a comment below!
Best regards, Konstantin
San Francisco, CA, June 3, 2020.
References
-
Opening The Ruby Concurrency Toolbox from Honeybadger
-
Ruby Concurrency and Parallelism: A Practical Tutorial first of the two TopTal blog posts I highly recommend.
-
The Many Interpreters and Runtimes of the Ruby Programming Language from the fantastic TopTal blog
And if you are looking for something a bit off Topic, checkout this somewhat controversial talk by Dave Thomas — The Future is Function, or, OO is Dead from 2017 RubyHACK Conference.
Comments