Rack Hijack works on unicorn/puma/ passenger
run
lambda{|env|
io
=
env['rack.hijack'].call
Thread.new
do
sleep
1
io.write
"HTTP/1.1
200\r\n"
io.write
"Connection:
close\r\n"
io.write
"Content-‐Length:
2\r\n"
io.write
"\r\n"
io.write
"OK"
io.close
end
[418,{},"NOT
DEFINED
IN
SPEC"]
}
%
ab
-‐c
100
-‐n
100
hOp://localhost:8080/
…
Percentage
of
the
requests
served
within
a
certain
4me
50%
1076
100%
1084
(longest
request)
100
concurrent
requests
All
sleep
for
1
second
Served
with
1.08
seconds
message_bus, ready for producEon
• Uses
rack.hijack
(and
thin.async
for
thin)
• In
produc4on
in
Discourse
for
2+
years
• Minimal
dependencies
(redis
and
rack
only)
• can
be
ported
to
pg
or
memory
• Runs
inside
your
Rails
app
as
middleware,
no
need
for
extra
ports
/
apps
What about AcEon Cable?
• No
code
available
to
review,
but
…
many
open
ques4ons
• Websockets
only
?!
• Reliable
message
ordering
?!
Ability
to
catch
up
?!
• Event
Machine?!
Celluloid?!
What about web sockets?
• Ini4al
version
supported
it
• Reliable
pub/sub
s4ll
required
• Fallback
logic
s4ll
required
(6
connec4on
per
browser,
less
on
phones)
• HTTPS
required
• HAProxy
hacks
may
be
required
• Hard
to
debug
• Not
significantly
beOer
than
long
polling
• PR
welcome
lru_redux
• LruRedux::TTL::Cache
–
Cache
with
4me-‐to-‐live
• Thread
safe
versions:
LruRedux::ThreadSafeCache
etc.
• Fastest
exis4ng
TTL
and
LRU
cache
for
Ruby
• Ruby
1.9
and
up
due
to
ordered
seman4cs
in
Hash
What if we had no AcEveRecord?
MemoryProfiler.report
do
raw_connection.exec("SELECT
price,
tax
FROM
products
LIMIT
10").values
.map{|row|
row.map{|col|
col.to_i}}
end.pretty_print
44
objects
allocated
(was
286)
3.7K
bytes
allocated
(was
25K)
21
Strings
allocated,
but
we
are
only
selec4ng
numbers,
WHY?
PG type mapping, new in pg 0.18
type_map
=
PG::BasicTypeMapForResults.new(raw_connection)
MemoryProfiler.report
do
result
=
raw_connection.exec("SELECT
price,
tax
FROM
products
LIMIT
10")
result.type_map
=
type_map
result.values
end.pretty_print
13
objects
allocated
(was
286)
1.1K
bytes
allocated
(was
25K)
Shout
out
to
Lars
Kanis
for
building
this
BackporEng Fast Pluck into Rails 4
• 87
lines
of
code
at:
discourse/lib/freedom_patches/ fast_pluck.rb
• 100%
backwards
compat
(works
on
Rails
4.1
/
4.2)
• Uses
new
PG
type
map
• Reduce
alloca4ons
from
286
to
198
• Reduce
memory
allocated
from
25K
to
18K
• Will
not
be
backported
into
Rails
4.2
The compelling sell of AcEveRecord
cars
=
Car.all
cars
=
cars.where(color:
color)
if
color
cars
=
cars.where('max_speed
>
?',
max_speed)
if
max_speed
cars
=
cars.select('make,
max_speed')
cars.each
do
|car|
puts
"make:
#{car.make}
max_speed:
#{car.max_speed}"
end
Can we do the same by hand?
sql
=
"select
*
from
cars
"
and_or_where
=
"where"
if
color
sql
<<
"where
color
=
'#{PG::Connection.escape(color)}'"
and_or_where
=
"and"
end
sql
<<
"#{and_or_where}
max_speed
=
'#{max_speed}'"
if
max_speed
connection.exec(sql).each
|row|
puts
"make:
#{row["make"]}
max_speed:
#{row["max_speed"]}"
end
SqlBuilder a sane alternaEve
builder
=
SqlBuilder.new("select
*
from
cars
/*where*/")
builder.where("color
=
:color",
color:
color)
if
color
builder.where("max_speed
=
:max_speed",
max_speed:
max_speed)
if
max_speed
builder.map_exec(Car).each
do
|row|
puts
"make:
#{row.make}
max_speed:
#{row.max_speed}"
end
How fast is this?
0
5
10
15
20
25
30
1
Row
100
Rows
1000
Rows
Raw
SqlBuilder
Ac4ve
Record
hOps://gist.github.com/SamSaffron/9077b632475a4fe0d57b
K
opera4on/sec
(2
columns
per
row)
“As
soon
I
started
measuring
I
no4ced
that
even
though
the
SQL
for
this
takes
12
milliseconds,
the
total
4me
it
takes
to
execute
the
above
code
is
much
higher,
profiling
shows
a
90
ms
execuHon
Hme.”
…
so
we
created
Dapper
…
What is Dapper?
public
class
Dog
{
public
int?
Age
{
get;
set;
}
public
Guid
Id
{
get;
set;
}
public
string
Name
{
get;
set;
}
public
float?
Weight
{
get;
set;
}
public
int
IgnoredProperty
{
get
{
return
1;
}
}
}
var
guid
=
Guid.NewGuid();
var
dog
=
connec4on.Query(
"select
Age
=
@Age,
Id
=
@Id",
new
{
Age
=
(int?)null,
Id
=
guid
});
AcEve Record needs Dapper
• A
simple
“ultra
efficient”
standalone
SQL
-‐>
object
mapper
• Small
code
base
• Standalone
gem
• Handle
parameters
• Interoperable
with
Rails
• All
queries
run
through
object
mapper
• Provide
a
“blessed”
efficient
SQL
story
Lightweight jobs
ObjectSpace.each_object(Unicorn::HttpServer)
do
|s|
s.extend(Scheduler::Defer::Unicorn)
end
module
Unicorn
def
process_client(client)
Defer.pause
super(client)
Defer.do_all_work
Defer.resume
end
end
Source maps in producEon
• Using
uglifyjs
directly
is
significantly
faster,
6x
faster
on
some
files
• GSOC
project
to
improve
this
• uglifyjs
command
line
makes
it
simple
to
add
source
maps
• Discourse
-‐>
lib/tasks/assets.rake
Anonymous Cache
• Dras4cally
improves
performance
• Before:
52
ms
per
request
to
home
page
• Auer:
1.6
ms
per
request
to
home
page
• Redis
backend
(redis.setex)
• lib/middleware/anonymous_cache.rb
Anonymous Cache
• Vast
majority
of
traffic
is
anonymous
• Caching
logic
is
tricky:
• Is
it
mobile?
• Is
it
a
web
crawler?
• Is
user
logged
on?
• Implemented
as
Rack
middleware,
early
in
chain
OpEmizing for Development
• rake
autospec
• beOer_errors
• rack-‐mini-‐profiler
• logster
• Fast
browser
reload
4mes
• Live
CSS
refresh
thanks
to
message_bus