Upload
others
View
36
Download
0
Embed Size (px)
Citation preview
RealtimeRails Four
Rails FourFor you and for me!
Aaron Patterson@tenderlove
HI!! :-)
AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.
Rails FourFor you and me!!!
The Web!
Ruby
Rails
Web
Thread SafetyIn Rails
Delete config.threadsafe!
Why delete?
Always bethread safe!
Always bethread safe!
MyOpinion!
Simplify
Is it safe to remove?
What did it do?
threadsafe!
❤ Preload frameworks (enable)❤ Cache classes (enable)❤ Dependency loading (disable)❤ Allow concurrency (enable)
Loading Code isn’t Thread-safe*
Preload Frameworks
ENABLED
Cache classes
ENABLED
Dependency Loading
DISABLED
Allow Concurrency
ENABLED
Get Lock
Read from Socket
Process Stuff
Write to Socket
Release Lock
Rack::Lock
Thread 2 Get Lock
Read from Socket
Process Stuff
Write to Socket
Release Lock
Thread 1
Rack::Lock
Thread 2 Get Lock
Read from Socket
Process Stuff
Write to Socket
Release Lock
Rack::Lock
Thread 2
Get Lock
Read from Socket
Process Stuff
Write to Socket
Release Lock
Rack::Lock
Rack::Lock with Processes
Rack::Lock withThreads
Best Case:Extra Overhead
Worst Case:1 req at a time
IMPACT
Boot time increases (in prod)
Fewer middleware
Multi-Proc serversstay the same
Threaded serversJust Work™
Bug Fixes
100% Caching
Locking ||=
def some_method @value ||= some_calculationend
check then act
Is nil?
Calculate
Return
Set
check then act
Is nil?
Calculate
Return
Thread 1
Set
check then act
Is nil?
Calculate
Return
Thread 1
Set
Thread 2
check then act
Is nil?
Calculate
Return
Thread 1 Set
Thread 2
check then act
Is nil?
Calculate
Return
Set
Fix #1 (eager init)class Foo class << self def hard_calculation @calc end end
@calc = fib(34)end
p Foo.hard_calculation
Fix #2 (locking)class Foo @lock = Mutex.new
class << self def hard_calculation @lock.synchronize do @calc ||= fib(34) end end endend
p Foo.hard_calculation
Fix #2 (locking)class Foo @lock = Mutex.new
class << self def hard_calculation @lock.synchronize do @calc ||= fib(34) end end endend
p Foo.hard_calculation
Fix #2 (locking)class Foo @lock = Mutex.new
class << self def hard_calculation @lock.synchronize do @calc ||= fib(34) end end endend
p Foo.hard_calculation
Move methods to instances
Move to objectclass Foo def initialize @calc = fib(34) end
def hard_calculation @calc endend
Foo.new.hard_calculation
Lazy Objectclass Foo include Mutex_m
def hard_calculation synchronize do @calc ||= fib(34) end endend
Foo.new.hard_calculation
Maintain APIclass Foo include Mutex_m
def hard_calculation synchronize do @calc ||= fib(34) end end
Instance = new
def self.hard_calculation Instance.hard_calculation endend
Foo.hard_calculation
Hash.new { }class Foo def initialize @cache = Hash.new { |h,k| h[k] = [] } end
def some_value(key) @cache[key] endend
Fix #1 (lock)class Foo include Mutex_m
def initialize super
@cache = Hash.new { |h,k| h[k] = [] } end
def some_value(key) synchronize { @cache[key] } endend
Fix #2 thread_safe
https://github.com/headius/thread_safe
What about the App Level?
Thread SafetyIn Web Apps
Avoid shared data
Most people don’t type “Thread.new”
Look for things that are global.
Global Variables
$so_global = {}$so_global[:foo] ||= “bar”
Constants
SET_TWICE = 10SET_TWICE = 10
ALSO_GLOBAL = {}ALSO_GLOBAL[:foo] ||= “bar”
Warning
No Warning
Class methods
class MyModel def self.some_cache @foo ||= fib(34) endend
Avoid global data
Add locks
Streaming
Template Rendering Today
Templates Results are Buffered
Clients are blocked while Rails works
The entire page must fit in memory
Rack Encourages Buffering
class MyApplication def call(env) [200, {}, [‘my page’]] endend
We can do I/O and CPU in parallel.
So why buffer?
ActionController::Live
Exampleclass BrowserController < ApplicationController include ActionController::Live
def index 100.times do response.stream.write "hello!\n" end response.stream.close endend
Exampleclass BrowserController < ApplicationController include ActionController::Live
def index 100.times do response.stream.write "hello!\n" end response.stream.close endend
Mix in
Stream
response.stream acts likean I/O object
EverythingIs a File
How does it work?
Our APIdef index response.status = 200 response.headers[‘X-Whatever’] = ‘<3’ response.stream.write ‘hello’ response.stream.write ‘ world’ response.stream.closeend
Rack API
def call(env) return [200, {‘X-Whatever’ => ‘<3’}, [‘hello world’]]end
Wrapped Requestclass Response attr_accessor :status attr_reader :headers, :stream
def initialize @status = 200 @headers = {} @stream = StringIO.new endend
def call(env) res = Response.new controller.response = res controller.index [res.status, res.headers, res.stream]end
Threaded action
def call(env) res = Response.new controller.response = res Thread.new { controller.index } [res.status, res.headers, res.stream]end
Block until writedef call(env) res = Response.new controller.response = res Thread.new { controller.index } res.stream.await [res.status, res.headers, res.stream]end
Block until writedef call(env) res = Response.new controller.response = res Thread.new { controller.index } res.stream.await [res.status, res.headers, res.stream]end
Block
Blocking Bufferclass Buffer def initialize @latch = Latch.new @buffer = Queue.new end
def await # wait for write @latch.await end
def write(str) @latch.release @buffer << str endend
Blocking Bufferclass Buffer def initialize @latch = Latch.new @buffer = Queue.new end
def await # wait for write @latch.await end
def write(str) @latch.release @buffer << str endend
`call` blocks here
Blocking Bufferclass Buffer def initialize @latch = Latch.new @buffer = Queue.new end
def await # wait for write @latch.await end
def write(str) @latch.release @buffer << str endend
`write` unblocks
What can we do?
Rails Internals
Streaming ERB
View Source
# encoding: utf-8
require 'erb'
doc = ERB.new '<%= hello %> world'puts doc.src
Source
#coding:UTF-8_erbout = ''; _erbout.concat(( hello ).to_s); _erbout.concat " world"; _erbout.force_encoding(__ENCODING__)
Control Outputclass MyERB < ERB def set_eoutvar(compiler, eoutvar = '_erbout') compiler.put_cmd = "#{eoutvar}.write" compiler.insert_cmd = "#{eoutvar}.write" compiler.pre_cmd = [] compiler.post_cmd = [] endend
doc = MyERB.new '<%= hello %> world', nil, nil, '$stdout'puts doc.src
Source
#coding:UTF-8$stdout.write(( hello ).to_s); $stdout.write " world"
$ ruby test.rbhello world$
Web Apps
Infinite Streams
Server Sent Events
SSE ResponseHTTP/1.1 200 OKX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffContent-Type: text/event-streamTransfer-Encoding: chunked
event: pingdata: {"ping":"2012-10-06T21:44:41-07:00"}
event: reloaddata: {"changed":["/Users/aaron/git/lolwut/app/views/users/"]}
SSE ResponseHTTP/1.1 200 OKX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffContent-Type: text/event-streamTransfer-Encoding: chunked
event: pingdata: {"ping":"2012-10-06T21:44:41-07:00"}
event: reloaddata: {"changed":["/Users/aaron/git/lolwut/app/views/users/"]}
SSE ResponseHTTP/1.1 200 OKX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffContent-Type: text/event-streamTransfer-Encoding: chunked
event: pingdata: {"ping":"2012-10-06T21:44:41-07:00"}
event: reloaddata: {"changed":["/Users/aaron/git/lolwut/app/views/users/"]}
SSE ResponseHTTP/1.1 200 OKX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffContent-Type: text/event-streamTransfer-Encoding: chunked
event: pingdata: {"ping":"2012-10-06T21:44:41-07:00"}
event: reloaddata: {"changed":["/Users/aaron/git/lolwut/app/views/users/"]}
Client SidejQuery(document).ready(function() { setTimeout(function() { var source = new EventSource('/control');
// if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1);});
Client SidejQuery(document).ready(function() { setTimeout(function() { var source = new EventSource('/control');
// if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1);});
Client SidejQuery(document).ready(function() { setTimeout(function() { var source = new EventSource('/control');
// if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1);});
Client SidejQuery(document).ready(function() { setTimeout(function() { var source = new EventSource('/control');
// if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1);});
Real-Time BrowserCommunication
Puma
BrowserFS-Events
FS Events
Puma
BrowserFS-Events
FS Events
Puma
BrowserFS-Events
FS Events
Puma
BrowserFS-Events
FS Events
Puma
BrowserConsole
DRB
DB Events
Puma
BrowserConsole
DRB
DB Events
Socket
Other Input Sources
❤ Embedded systems (sausage box)❤ Telephony (twilio)❤ Other users (chat systems)
Thread Safety
P72NStreaming
Cores are increasing.
We need to utilize the entire machine
High latency clients are increasing.
Patience is decreasing.
Lie.Using cached data
Cheat.Updating partial parts of the page
Steal.Move computations to client side via JS
Lie.Cheat.Steal.
Be GoodEngineers
THANK YOU
Questions?