Fast, highly concurrent web applications with no callbacks!
Sinatra::Synchrony is a small extension for Sinatra that (when used properly) improves the concurrency of your web application processes. Powered by EventMachine and EM-Synchrony, it increases the number of clients your application can serve per process when you have a lot of traffic and slow IO calls (like HTTP calls to external APIs). Because it uses Fibers internally to handle blocking IO, no callback gymnastics are required! Just develop as if you were writing a normal Sinatra web application, use non-blocking libraries (see below) and you're all set.
This project was initially introduced at the Portland Ruby Brigade. The original slideshow is available online. Recently, I've been telling people to use threads instead of reactor patterns, and I think you should not use this unless you absolutely have to (see Caveats).
Install the gem:
gem install sinatra-synchrony
Register with Sinatra at the top, before any other middleware or plugins are loaded:
require 'sinatra/base' require 'sinatra/synchrony' class App < Sinatra::Base register Sinatra::Synchrony end
If you are developing with a classic style app, just require the gem and it will automatically load:
require 'sinatra' require 'sinatra/synchrony' get '/' do 'Sinatra::Synchrony is loaded automatically in classic mode, nothing needed' end
Despite enabling synchronous programming without callbacks, there is no performance hit to your application! All the performance benefits you expect from Thin/Rainbows and EventMachine are still there:
class App < Sinatra::Base
register Sinatra::Synchrony
get '/' do
'Hello World!'
end
end
Benchmarked with rackup -s thin:
$ ab -c 50 -n 2000 http://127.0.0.1:9292/
...
Requests per second: 3102.30 [#/sec] (mean)
Time per request: 16.117 [ms] (mean)
Time per request: 0.322 [ms] (mean, across all concurrent requests)
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 1
Processing: 5 16 7.7 13 38
Waiting: 3 13 7.0 10 35
Total: 6 16 7.7 13 38
Let's try a simple blocking IO example to prove it works. 100 hits to google.com:
require 'sinatra' require 'sinatra/synchrony' require 'rest-client' require 'faraday' Faraday.default_adapter = :em_synchrony get '/' do Faraday.get 'http://google.com' end
$ ab -c 100 -n 100 http://127.0.0.1:9292/ ... Time taken for tests: 0.256 seconds
For a perspective, this operation took 33 seconds without this extension in thin.
This gem was designed to help us develop faster games and internal applications for Geoloqi: a private, real-time mobile and web platform for securely sharing location data. We wanted to share with you how we deal with concurrency issues, and also make it easy to utilize this for our other projects. One of these projects is our recently released Geoloqi ruby adapter, which utilizes Faraday and sinatra-synchrony to provide massive concurrency with almost no changes required to your code. Geoloqi is production ready right now, and we have a lot of major features and enhancements coming very soon.
This extension does not provide non-blocking IO drivers for everything. Right now the focus is to improve the performance of our ruby applications by enabling cooperative scheduling without resorting to callbacks. You don't have to make everything non-blocking to speed up applications with this approach, which is the important thing to understand. For example, if your database access blocks and is under ten milliseconds, it's not as bad as a blocking API call to an external web site that takes a few seconds.
There are numerous non-blocking IO drivers available for EventMachine. Check out the Protocol Implementations page on the EventMachine GitHub Wiki for a full list. I would personally like to see plug-and-play drivers implemented for the major three ORMs (ActiveRecord, DataMapper, Sequel). There are solutions for direct access though. For example, Mysql2 has excellent support for EventMachine, and I believe there are solutions for PostgreSQL as well. Learning how to do Fiber wrapping will empower you to understand how to make any async driver support this.
Please also keep in mind that MRI Ruby 1.9.2 (YARV) does have an internal mechanism for running some IO requests in a non-blocking manner similar to this as long as you're using GIL threads and are not using EventMachine (and this includes any usage of Thin). Dig through the source code for rb_thread_blocking_region if you're interested in exploring this further. If you're using Ruby 1.8, you should seriously consider switching to 1.9 YARV (1.8 also does this, but 1.9 is far better at it). I have an awesome configuration script for Rainbows which complements this approach with a thread pool. I also have a config script for Rainbows and EventMachine.
So why is sinatra-synchrony useful if Ruby doesn't block on IO? Well, turning off the thread overhead can provide a very strong performance boost for IO work. So I would describe this as a tool for squeezing every last bit out of your web applications. And if you code it right, it's easy to switch back to GIL threads (or something with real threads like Rubinius 2.0 or JRuby). The important thing in any programming environment is to have options. Here is an option for you. Use it wisely.