This document summarizes Kyle Drake's presentation on using EventMachine (EM) and EM::Synchrony to build fast, concurrent Ruby web applications without blocking I/O or callback hell. Key points include:
- EM implements the reactor pattern to handle blocking I/O without threads by using callbacks and kernel threads.
- EM::Synchrony avoids callback nesting by wrapping callbacks in fibers, allowing synchronous-looking code.
- Sinatra can be made concurrent by running each request in its own EM::Synchrony fiber with little code change.
- Ruby has strong options for building high-performance concurrent apps while maintaining productivity advantages over Node.js.
2. A bit about me.
• Full time Facebook developer
• All FB apps: Sinatra + DM, MRI + Thin on EY, Heroku
• Lots and lots of slow API calls to Facebook
• Lots and lots of delayed timeouts, API errors
• Lots and lots of users
• Lots and lots of performance issues from slow API
calls!
4. MapAttack!
• Over 60 hits per second
• Lots of network API calls to Geoloqi platform
• Lots of scary thread exceptions (Rainbows! +
ThreadSpawn)
• Completely unsustainable as a conventional
ruby web application
5. What’s going on here?
• Blocking IO!
• Process spends 90% of time waiting, 10% actually
doing something
• Raw performance (Typheous vs Net::HTTP) is
almost irrelevant! It’s not your bottleneck.
• MRI: No real threads, so IO blocks. 1.8 has green
threads, 1.9 has kernel threads with GIL
• JRuby: Real kernel threads, much better. But slow
(before JIT warms up), special deploy stack, and...
6.
7.
8. Based on the Reactor pattern..
No threads (sortof)
No blocking IO at all
Uses callbacks as events
var app = express.createServer();
app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
Is this the future of web development?
9. CALLBACK HELL
var useFile = function(filename,callback){
posix.stat(filename).addCallback(function (stats) {
posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) {
posix.read(fd, stats.size, 0).addCallback(function(contents){
callback(contents);
});
});
});
};
I found far worse examples, but
they wouldn’t fit on this page.
10. New feature: Joyent Owns It
“Any sort of use in commerce, such as promoting a
Platform-as-a-Service or professional services
offering by using the Node.js mark, does require a
written license agreement.”
“If Joyent notifies you that your use of any trademark
is detrimental to any Joyent trademarks or is
otherwise unacceptable, you must immediately cease
using the marks, blah blah...”
11.
12. What does Node.js do?
It uses the Reactor pattern.
Can we copy it?
YES!
13. The Reactor Pattern
“The reactor design pattern is a concurrent
programming pattern for handling service
requests delivered concurrently to a service
handler by one or more inputs” - Wikipedia
What Node.js does: Takes your blocking IO
operation, shoves it into its own kernel thread
behind the scenes, uses Unix kernel magic to make
it rejoin the reactor queue when it’s ready.
• Linux: epoll(4)
• BSD: kqueue/kevent
14. • Blocking IO a UNIVERSAL problem
• All programming languages have trouble with it
• The Reactor pattern resolves it
• Most languages have the Reactor pattern!
15. • Blocking IO a UNIVERSAL problem
• All programming languages have trouble with it
• The Reactor pattern resolves it
• Most languages have the Reactor pattern!
JavaScript Node.js
Python Twisted
Ruby EventMachine (libem, C)
Java JBoss_Netty
(wait, what? I thought Java had good threading..)
PHP None yet (perhaps ever)
16. EventMachine
• Stable, fast, mature, works!
• Production tested
• Thin has EM built in
• Hosting Support (CloudFoundry, Heroku)
# Source: http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/
EventMachine.run {
page = EventMachine::HttpRequest.new('http://google.ca/').get
page.errback { p "Google is down! terminate?" }
page.callback {
about = EventMachine::HttpRequest.new('http://google.ca/search?q=eventmachine').get
about.callback { # callback nesting, ad infinitum }
about.errback { # error-handling code }
}
}
17. EventMachine
The Bad
• Documentation is weak
• It’s weird for synchronous programmers
• Education problem - nobody understands it
• Like Node.js, it requires callback programming
• Callbacks don’t play nicely with web frameworks
without nasty hacks (async-sinatra, throw :async,
et cetera)
18. EventMachine
What if I told you you could do
concurrent asynchronous
programming, WITHOUT
CALLBACKS?
19. EM-Synchrony
• http://github.com/igrigorik/em-synchrony
• “Collection of convenience classes and
primitives to help untangle evented code,
plus a number of patched EM clients to make
them Fiber aware”
• Wraps callbacks in Ruby 1.9 Fibers
automatically via EM::Synchrony.sync. The
result: NO CALLBACKS!
• Anything with a callback method can be
patched instantly to support this.
20. EM-Synchrony
def http_get(url)
f = Fiber.current
http = EventMachine::HttpRequest.new(url).get
# resume fiber once http call is done
http.callback { f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EventMachine.run do
Fiber.new{
page = http_get('http://www.google.com/')
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get('http://www.google.com/search?q=eventmachine')
puts "Fetched page 2: #{page.response_header.status}"
end
}.resume
end
21. EM-Synchrony
EventMachine.synchrony do
page = EventMachine::HttpRequest.new("http://www.google.com").get
p "No callbacks! Fetched page: #{page}"
EventMachine.stop
end
22. EM-Synchrony
Goliath
http://postrank-labs.github.com/goliath
require 'goliath'
class Hello < Goliath::API
# reload code on every request in dev environment
use ::Rack::Reloader, 0 if Goliath.dev?
def response(env)
[200, {}, "Hello World"]
end
end
# > ruby hello.rb -sv
# > [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000
23. EM-Synchrony
Goliath
• Awesome! But...
• Designed for SOA work, not high-level web
development
• Doesn’t play nicely with Rack, Thin, Heroku
• It’s re-inventing the wheel (I love Sinatra!)
• Can we make Sinatra work with EM-Synchrony?
YES!
24. Sinatra-Synchrony
http://github.com/kyledrake/sinatra-synchrony
• Tiny glue extension, < 100 LOC
• Same old Sinatra, concurrency is (mostly) built in.
• EventMachine and EM-Synchrony, Rack, Thin, Rainbows!,
CloudFoundry, Heroku
• Wraps each request in its own Fiber
• Only coding change is to use non-blocking drivers/libraries
• Patches Rack::Test to make tests run within EM-Synchrony fiber
Wow, that was easy.
25. Conclusions
• You can bake strong concurrency support into Ruby
with almost zero changes to your code
• You can take advantage of this while programming
synchronously as usual.. Node.js can’t!
• This makes Ruby a real competitor here. Strong
performance, concurrency, maintainability, productivity,
testing
• RUBY IS NOT SLOW. This is a marketing failure, and
we need to fix it.
26. What You Can Do
• Try this stuff out! Play with it. Teach others how to use
it. Work on the code for it.
• Help me with this Sinatra-Synchrony idea. Perhaps we
can make the idea more general purpose? Rails
support?
• DEFEND RUBY FROM THE “RUBY IS SLOW”
PEOPLE. Sinatra-Synchrony gets 3000 hits per second
in benchmarks on my MacBook with OSX’s crappy
network stack, on one core. Productivity and
performance are not incompatible here.
• It’s my birthday today, get me drunk.
27. The Future
Rubinius - Hydra Branch
http://rubini.us/2011/02/17/rubinius-what-s-next
• The Smalltalk-80 Blue Book approach is working
• They are trashing their GIL with pure ruby!
• Supports EventMachine, Fibers coming soon
• Because of Evan (and friends), Alan Kay, Ezra,
RUBINIUS IS THE FUTURE