0-60 with Goliath: Building High Performance Ruby Web-Services
1. 0-60 with Goliath building high-performance (Ruby) web services Ilya Grigorik @igrigorik
2. conn =EM::HttpRequest.new('http://gogaruco.com') r1 =conn.get :path => "speakers.html", :keepalive => true# 250 ms r2 =conn.get :path => "schedule.html"# 300 ms # wait until done … Answer: All of the above! Total execution time is: 250 ms 300 ms 550 ms ~ 65% truthiness ~ 25% truthiness * ~ 10% truthiness ** HTTP Quiz this is not a trick question…
3. “SPDY? We can’t even get HTTP right…” the why, how, and how-to of Goliath
4.
5. Benchmark client RTT, not just the server processing time A public service announcement…
12. + 40ms TCP setup (network) + 20ms request (network) + 40ms processing + 20ms response (network) 60% of time in network overhead 120ms for two requests – 50% improvement! Pipelining RFC 2616 (1999)
13. Connection setup: 50ms Request 1: 300ms Request 2: 250ms Total time: ~250 ms ~300 ms ~350 ms ~600 ms Pipelining Quiz RFC 2616 (1999)
14. There is just one small gotcha… Making HTTP Pipelining Usable on the Open Web http://tools.ietf.org/html/draft-nottingham-http-pipeline-01
15. conn =EM::HttpRequest.new('http://gogaruco.com') r1 =conn.get :path => "speakers.html", :keepalive => true# 250 ms r2 =conn.get :path => "schedule.html"# 300 ms Total execution time is: 250 ms 300 ms 550 ms Keep-alive what? HTTP 1.0! ~ 65% truthiness ~ 25% truthiness * ~ 10% truthiness ** Good: Keep-alive + Pipelining Bad: Keep-alive + Garbage “I’m confused” HTTP in the wild it’s a sad state of affairs Keep-alive: mostly works – yay! Pipelining: disabled (except in Opera)
16. HTTP can be a high-performance transport Goliath is our attempt to make it work
39. require'goliath' classHello< Goliath::API defresponse(env) [200, {}, "Hello World"] end end $>ruby hello.rb-sv –p 8000 –e production Hello World Simple Goliath server
40. classHello< Goliath::API use Goliath::Rack::Params use Goliath::Rack::JSONP use Goliath::Rack::Validation::RequestMethod, %w(GET) use Goliath::Rack::Validation::RequiredParam, {:key => 'echo'} defresponse(env) [200, {}, {pong: params['echo’]}] end end Middleware No rackup file
41. classBonjour< Goliath::API defresponse(env) [200, {}, "bonjour!"] end end classRackRoutes< Goliath::API map '/version'do run Proc.new { |env| [200, {}, ["Version 0.1"]] } end get "/bonjour", Bonjour not_found('/') do # run Proc. new { ... } end end Routing simple, but powerful
42. classAsyncUpload< Goliath::API defon_headers(env, headers) env.logger.info'received headers: '+headers end defon_body(env, data) env.logger.info'received data chunk: '+ data end defon_close(env) env.logger.info'closing connection' end defresponse(env) # called when request processing is complete end end Async Request Processing don’t need to wait for the full request…
43. classStream< Goliath::API defresponse(env) pt=EM.add_periodic_timer(1) { env.stream_send("hello") } EM.add_timer(10) do pt.cancel env.stream_send("goodbye!") env.stream_close end streaming_response 202, {'X-Stream' => 'Goliath’} end end Async/Streaming Response don’t need to render full response…
44. * Goliath::SPDY classWebsocket< Goliath::WebSocket defon_open(env) env.logger.info”WebSocket opened” end defon_message(env, msg) env.logger.info”WebSocket message: #{msg}” end defon_close(env) env.logger.info”WebSocket closed” end defon_error(env, error) env.logger.error error end end Web-Sockets simple backend extension
61. describe HttpLogdo it 'forwards to our API server'do with_api(HttpLog, api_options) do |api| get_request({}, err) do |c| c.response_header.status.should== 200 c.response_header[’X-Header'].should =='Header' c.response.should=='Hello from Responder' end end end end Integration Testing simple end-to-end testing