Rails стал отличным ответом на требования многих лет опыта использования классической процессной модели веб-запросов. Такая модель все еще является наиболее надежной и простой для понимания и контроля. Но новое поколение высокодинамичных и интерактивных веб приложений требует принципиально новых требований к масштабированию. Одним из ответов на такие требования может стать сервис Pusher.com, который, в числе прочих вариантов решений, будет рассмотрен в этом докладе
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Understanding the Rails web model and scalability options
1. “ Genius is the gold in the mine; talent is
the miner who works and brings it out. ”
Lady Marguerite Blessington
Donets Basin
Coal Mine-Ukraine
2. “ Genius is the gold in the mine; talent is
the miner who works and brings it out. ”
Lady Marguerite Blessington
Презентация начнется в
течение нескольких минут ...
Donets Basin
Coal Mine-Ukraine
18. require 'rubygems'
require 'rack'
class Test
def call(env)
sleep 1 # on purpose, simulating a blocking operation
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
19. require 'rubygems'
require 'rack'
class Test
def call(env)
sleep 1 # on purpose, simulating a blocking operation
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
20. $ rackup config.ru
>> Thin web server (v1.3.1 codename Triple Espresso)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:8080, CTRL+C to stop
21. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 10.020 seconds
...
Requests per second: 1.00 [#/sec] (mean)
Time per request: 1002.015 [ms] (mean)
Time per request: 1002.015 [ms] (mean, across all concurrent requests)
Transfer rate: 0.12 [Kbytes/sec] received
...
22. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 10.020 seconds
...
Requests per second: 1.00 [#/sec] (mean)
Time per request: 1002.015 [ms] (mean)
Time per request: 1002.015 [ms] (mean, across all concurrent requests)
Transfer rate: 0.12 [Kbytes/sec] received
...
23. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 10.020 seconds
...
Requests per second: 1.00 [#/sec] (mean)
Time per request: 1002.015 [ms] (mean)
Time per request: 1002.015 [ms] (mean, across all concurrent requests)
Transfer rate: 0.12 [Kbytes/sec] received
...
24. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 10.020 seconds
...
Requests per second: 1.00 [#/sec] (mean)
Time per request: 1002.015 [ms] (mean)
Time per request: 1002.015 [ms] (mean, across all concurrent requests)
Transfer rate: 0.12 [Kbytes/sec] received
...
25. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 10.020 seconds
...
Requests per second: 1.00 [#/sec] (mean)
Time per request: 1002.015 [ms] (mean)
Time per request: 1002.015 [ms] (mean, across all concurrent requests)
Transfer rate: 0.12 [Kbytes/sec] received
...
30. Ruby Enterprise Edition 1.8.7 Copy on Write patched
(about to be deprecated) mark and sweep GC
Ruby 1.9.2 (current) mark and sweep GC
Ruby 1.9.3 (transitioning) Lazy Sweep GC
Copy on Write compatible
Ruby 2.0 (future)
Bitmap Marking GC
31. Ruby Enterprise Edition 1.8.7 Copy on Write patched
(about to be deprecated) mark and sweep GC
Ruby 1.9.2 (current) mark and sweep GC
Ruby 1.9.3 (transitioning) Lazy Sweep GC
Copy on Write compatible
Ruby 2.0 (future)
Bitmap Marking GC
32. Ruby Enterprise Edition 1.8.7 Copy on Write patched
(about to be deprecated) mark and sweep GC
Ruby 1.9.2 (current) mark and sweep GC
Ruby 1.9.3 (transitioning) Lazy Sweep GC
Copy on Write compatible
Ruby 2.0 (future)
Bitmap Marking GC
33. Ruby Enterprise Edition 1.8.7 Copy on Write patched
(about to be deprecated) mark and sweep GC
Ruby 1.9.2 (current) mark and sweep GC
Ruby 1.9.3 (transitioning) Lazy Sweep GC
Copy on Write compatible
Ruby 2.0 (future)
Bitmap Marking GC
41. Notifications and Events in general
Online Gaming
Chatting and Presence
Collaborative Applications
Advanced and more Interactive UI
42. (function poll(){
$.ajax({ url: "server", success: function(data){
// do something with the received ‘data’
//Setup the next poll recursively
poll();
}, dataType: "json"});
})();
43. (function poll(){
setTimeout(function(){
$.ajax({ url: "server", success: function(data){
// do something with the received ‘data’
//Setup the next poll recursively
poll();
}, dataType: "json"});
}, 5000);
})();
48. W3C/IETF Standard - RFC 6455
(12/2011)
Full duplex persistent communication channel
Less overhead than HTTP
(down to 2 bytes per frame)
No latency (no reconnections)
No polling overhead (on demand)
“Upgrades” HTTP, uses 80/443
(kind of friendly to existing proxies)
74. require 'rubygems'
require 'rack'
class Test
def call(env)
sleep 1 # on purpose, simulating a blocking operation
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
75. require 'rubygems'
require 'rack'
class Test
def call(env)
EM.defer do
sleep 1 # CPU bound, throw to thread-pool (cheating)
end
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
76. require 'rubygems'
require 'rack'
class Test
def call(env)
EM.defer do
sleep 1 # CPU bound, throw to thread-pool (cheating)
end
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
77. require 'rubygems'
require 'rack'
class Test
def call(env)
EM.defer do
sleep 1 # CPU bound, throw to thread-pool (cheating)
end
[200, {"Content-Type" => "text/plain"},
["Hello World!"]]
end
end
Rack::Handler::Thin.run Test.new
78. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
79. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
80. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
81. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
82. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
83. ab -n 10 -c 1 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
#ZOMG!
...
Concurrency Level: 1
Time taken for tests: 0.003 seconds
...
Requests per second: 3219.58 [#/sec] (mean)
Time per request: 0.311 [ms] (mean)
Time per request: 0.311 [ms] (mean, across all concurrent requests)
Transfer rate: 380.44 [Kbytes/sec] received
...
84. ab -n 10 -c 10 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 10
Time taken for tests: 0.002 seconds
...
Requests per second: 5211.05 [#/sec] (mean)
Time per request: 1.919 [ms] (mean)
Time per request: 0.192 [ms] (mean, across all concurrent requests)
Transfer rate: 615.76 [Kbytes/sec] received
...
85.
86. Block Non-Block
Total Time 10 sec 0.0001 sec
Requests per
Second 1 + 8000
87. Block Non-Block
Total Time 10 sec 0.0001 sec
Requests per
Second 1 + 8000
88. Block Non-Block
Total Time 10 sec 0.0001 sec
Requests per
Second 1 + 8000
90. require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class EchoServer < EM::Connection
def receive_data(data)
if data.strip =~ /[exit|quit]$/i
EM.stop
else
send_data("Repeating: #{data}")
end
end
end
91. require 'rubygems' # or use Bundler.setup
require 'eventmachine'
class EchoServer < EM::Connection
def receive_data(data)
if data.strip =~ /[exit|quit]$/i
EM.stop
else
send_data("Repeating: #{data}")
end
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") { EM.stop }
Signal.trap("TERM") { EM.stop }
EM.start_server("0.0.0.0", 10000, EchoServer)
end
92. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
93. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
Repeating: Hello World
94. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
Repeating: Hello World
Play again, Sam
95. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
Repeating: Hello World
Play again, Sam
Repeating: Play again, Sam
96. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
Repeating: Hello World
Play again, Sam
Repeating: Play again, Sam
quit
.
97. $ telnet localhost 10000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World
Repeating: Hello World
Play again, Sam
Repeating: Play again, Sam
quit
Connection closed by foreign host.
105. ab -n 1000 -c 5 http://127.0.0.1:9876/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 5
Time taken for tests: 0.124 seconds
...
Requests per second: 8035.36 [#/sec] (mean)
Time per request: 0.622 [ms] (mean)
Time per request: 0.124 [ms] (mean, across all concurrent requests)
Transfer rate: 588.53 [Kbytes/sec] received
...
107. ab -n 1000 -c 5 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1178079 $>
...
Concurrency Level: 5
Time taken for tests: 0.121 seconds
..
Requests per second: 8239.06 [#/sec] (mean)
Time per request: 0.607 [ms] (mean)
Time per request: 0.121 [ms] (mean, across all concurrent requests)
Transfer rate: 973.56 [Kbytes/sec] received
...
108.
109. Node.js Ruby
libev Eventmachine, Cool.io
Google V8 MRI, JRuby, Rubinius
Less Resources, Faster More Resources, Slightly
Processing slower processing
callbacks only Threads, Fibers, Callbacks
110. Node.js Ruby
libev Eventmachine, Cool.io
Google V8 MRI, JRuby, Rubinius
Less Resources, Faster More Resources, Slightly
Processing slower processing
callbacks only Threads, Fibers, Callbacks
111. Node.js Ruby
libev Eventmachine, Cool.io
Google V8 MRI, JRuby, Rubinius
Less Resources, Faster More Resources, Slightly
Processing slower processing
callbacks only Threads, Fibers, Callbacks
112. Node.js Ruby
libev Eventmachine, Cool.io
Google V8 MRI, JRuby, Rubinius
Less Resources, Faster More Resources, Slightly
Processing slower processing
callbacks only Threads, Fibers, Callbacks
113. Node.js Ruby
libev Eventmachine, Cool.io
Google V8 MRI, JRuby, Rubinius
Less Resources, Faster More Resources, Slightly
Processing slower processing
callbacks only Threads, Fibers, Callbacks
117. fs.rename('/tmp/hello', '/tmp/world', function (err) {
if (err) throw err;
console.log('renamed complete');
});
fs.stat('/tmp/world', function (err, stats) {
if (err) throw err;
console.log('stats: ' + JSON.stringify(stats));
});
118. fs.rename('/tmp/hello', '/tmp/world', function (err) {
if (err) throw err;
fs.stat('/tmp/world', function (err, stats) {
if (err) throw err;
console.log('stats: ' + JSON.stringify(stats));
});
});
119. EM.run do
page = EM::HttpRequest.new(@url1).get
page.errback do
puts "Site is down! terminate?"
end
page.callback do
about = EM::HttpRequest.new(@url2).get
about.callback do
# callback nesting, ad infinitum
end
about.errback do
# error-handling code
end
end
end
123. number_generator = Fiber.new do
start = 0
loop do
start += 1
Fiber.yield(start)
end
end
> number_generator.resume
=> 1
124. number_generator = Fiber.new do
start = 0
loop do
start += 1
Fiber.yield(start)
end
end
> number_generator.resume
=> 1
> number_generator.resume
=> 2
125. number_generator = Fiber.new do
start = 0
loop do
start += 1
Fiber.yield(start)
end
end
> number_generator.resume
=> 1
> number_generator.resume
=> 2
> number_generator.resume
=> 3
126.
127. Less expensive than Threads
cooperative vs preemptive multitasking
developer controls scheduling
no need to have mutexes, no shared data
coroutines / can be implemented with continuations
128. Less expensive than Threads
cooperative vs preemptive multitasking
developer controls scheduling
no need to have mutexes, no shared data
coroutines / can be implemented with continuations
129. Less expensive than Threads
cooperative vs preemptive multitasking
developer controls scheduling
no need to have mutexes, no shared data
coroutines / can be implemented with continuations
130. Less expensive than Threads
cooperative vs preemptive multitasking
developer controls scheduling
no need to have mutexes, no shared data
coroutines / can be implemented with continuations
131. Less expensive than Threads
cooperative vs preemptive multitasking
developer controls scheduling
no need to have mutexes, no shared data
coroutines / can be implemented with continuations
132. EM.run do
page = EM::HttpRequest.new(@url1).get
page.errback do
puts "Site is down! terminate?"
end
page.callback do
about = EM::HttpRequest.new(@url2).get
about.callback do
# callback nesting, ad infinitum
end
about.errback do
# error-handling code
end
end
end
133. page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
134. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
135. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
136. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
137. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
138. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
139. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
140. def http_get(url)
f = Fiber.current
http = EM::HttpRequest.new(url).get
http.callback{ f.resume(http) }
http.errback { f.resume(http) }
return Fiber.yield
end
EM.run do
Fiber.new do
page = http_get(@url1)
puts "Fetched page: #{page.response_header.status}"
if page
page = http_get(@url2)
puts "Fetched page 2: #{page.response_header.status}"
end
do.resume
end
142. EM.synchrony do
page = EM::HttpRequest.new("http://www.google.com").get
puts "Look Ma! No callbacks! Fetched page: #{page}"
EM.stop
end
# old way
EM.run do
page = EM::HttpRequest.new("http://www.google.com").get
page.callback do
puts "Lame ... Fetched page: #{page}"
EM.stop
end
end
144. require 'goliath'
require 'yajl'
G = Goliath::Rack # don’t to this, just to fit in this slide :-)
class Echo < Goliath::API
use G::Render, 'json' # auto-negotiate response format
use G::Params # parse & merge query and body parameters
use G::Validation::RequiredParam, {:key => 'echo'}
def process_request
logger.info "Processing request"
{response: env.params['echo']}
end
def response(env)
[200, {}, process_request]
end
end
149. Web App Server and App Framework
Fully asynchronous using Eventmachine
Lightweight and High Performance (+ 3k req/s in 1 process)
Rack aware (but not 100% Rack compatible)
Fibers with EM-Synchrony for easier development
152. Ruby can using Reactors to massively scale
Good contender to Node.js-style
(getting better)
One single Ruby process can handle thousands of
concurrent requests
Web browsers got smarter with Websockets
Ruby implements Websocket support
176. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
177. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
178. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
179. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
180. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
181. CGI model is difficult to scale
Multi-processes vs multi-threads vs Reactors
HTTP 5 WebSockets is hot!
Eventmachine is great!
Fibers vs “callback spaghetti”
Try out Pusher!
187. Rev (later Cool.io), Revactor
Reia
(Ruby syntax over Erlang, replaced by Elixir)
Celluloid (Threads abstraction to Actors)
(Mark Perham used to create Sidekiq)
193. Native threads
Ruby 1.9.x
(extensions can release the GIL)
Native threads
JRuby
(no GIL)
Native threads
Rubinius (no GIL
check out the Puma webserver)
194. Native threads
Ruby 1.9.x
(extensions can release the GIL)
Native threads
JRuby
(no GIL)
Native threads
Rubinius (no GIL
check out the Puma webserver)
195. Native threads
Ruby 1.9.x
(extensions can release the GIL)
Native threads
JRuby
(no GIL)
Native threads
Rubinius (no GIL
check out the Puma webserver)