notes for streaming responses with Rails 3.1

Eric Wong normalperson at
Wed Apr 27 18:32:43 EDT 2011

(I'll probably turn this into RDoc and put it on the website somewhere
after some editing, comments greatly appreciated as always)

Since there's increased interest with streaming responses with Rails 3.1
around the corner and Rainbows! was always designed with streaming
responses in mind, I'll suggest things I believe to be useful for use
with Rails 3.1 streaming.

Rails 3.1 streaming is primarily to reduce latency to clients even with
fast responses.   You want to be sending data to clients as quickly as
possible and buffer as little as possible on the server side.

This focuses on Ruby 1.9 since it's required for Rails 3.1 streaming
and Linux/NPTL since that's what I have the most experience on.

== Thread-safety

First off, enable thread-safety in Rails, most options here involve
threading in some way and Ruby threading (especially in 1.9.2+) isn't
nearly as bad as some people think[1].

== Rainbows! concurrency options

=== ThreadPool/ThreadSpawn

Both of these work pretty well in a standalone configuration (without
nginx in front).  Your entire app and all its dependencies *must* be

=== WriterThreadSpawn/WriterThreadPool

These /may/ offer more consistent performance if your application (but
not rendering) is CPU-intensive as they can better balance requests with
multiple cores and more worker_processes.

Both of these require nginx in front to be effective against slow client
requests.  However, nginx users should use "proxy_buffering off" in their
nginx config file or "X-Accel-Buffering: no" in their Rack responses to
stream responses.  This allows parts of the response to get to the
client as quickly as possible.

All code accessed in your Rails views (helpers, possibly
models/controllers) must be thread-safe, but some controller-only code
does not require thread-safety.  If your middleware wraps responses,
those must be thread-safe, too.  It might just be as easy to make your
entire application thread-safe and worry less later on :)

== Hardware/Environment

=== Memory

64-bit is recommended as you can run more native threads with Ruby 1.9.
All you need is addressable (not physical) memory to run more threads
with NPTL in Linux.  Ruby 1.9.2 requires 512KB of addressable stack per
thread, but hopefully 1.9.3 can will have a smaller stack size[3].
Most threads are likely to need <= 64KB of physical stack memory.

When choosing hardware, look for a fast memory/bus and large CPU caches.

=== Context-switching costs

I suspect this is negligible compared to the overhead of a high-level
language like Ruby in the first place.  Feedback/measurements with
actual Ruby/Rails application processes/threads would be greatly

Maybe play around with `schedtool` or `taskset` in Linux if you feel
it's a problem.  Perhaps pinning all native threads within a process to
the same CPU core is the best option to reduce contention for the GVL,
but the kernel scheduler may already be smart enough to do that.

More analysis is definitely needed in this area....

=== worker_connections

This is typically the maximum number of threads you'll spawn.

worker_connections * worker_processes is the total number of connections
you can have on Rainbows!

=== worker_processes

It's safe to increase worker_processes as long as you have *physical*
RAM for them.  Having more worker_processes will amortize the cost of
running GC and increase the amount of work multiple CPUs can do given
the GVL.

Whatever you do, make sure processes don't get swapped out to disk.

More coming later...

[1] - if you use any Free/Open Source C extensions[2] that don't work
properly with 1.9 native threads, feel welcome to ask for my help
publically or privately via email.

[2] - I'll only help if the *entire* stack for the extensions is
Free/OSS.  It cannot be a binding to a non-Free/OSS library which I
cannot look at/hack the source for.


Eric Wong

More information about the rainbows-talk mailing list