SLIDE 1 Rails Performance
Michael Koziarski michael@koziarski.com
SLIDE 2
Rails Performance
SLIDE 3
Relax
SLIDE 4
Programmers Love Optimisation
SLIDE 5
Science Based
SLIDE 6
Objective
SLIDE 7
Provable
SLIDE 8
Opportunity Cost
SLIDE 9
Is this optimisation really the best use of your time?
SLIDE 10
Two Justifications
SLIDE 11
Hardware Costs
SLIDE 12
Responsiveness
SLIDE 13
Hardware Costs
SLIDE 14
Profits = Revenue - Costs
SLIDE 15
Hardware isn’t all of your costs
SLIDE 16
Hardware costs are not continuous
SLIDE 17
Hardware costs are not continuous
Unless you’re huge
SLIDE 18
Hardware costs are not continuous
Unless you’re huge You’re not huge
SLIDE 19
Hardware is basically free
SLIDE 20
Hardware is basically free
Compared to staff
SLIDE 21
Hardware Cost Driven Optimisation
SLIDE 22 Example.com
- 4 Servers at $299/mo
- Total hosting of ~ $1,200/mo
SLIDE 23 Example.com
- 10% performance improvement across the
board!
- Leads to a $120/mo saving...
SLIDE 24 Example.com
- Staff costing 60-80k/yr
- $28-$38/hr
SLIDE 25
It almost certainly wasn’t worth it
SLIDE 26
Responsiveness
SLIDE 27
Much more Important
SLIDE 28
Slow applications are no fun to use
SLIDE 29
No Users
SLIDE 30
No Revenue
SLIDE 31
No Business
SLIDE 32
SLIDE 33
Not about requests per second
SLIDE 34
Seconds per request
SLIDE 35
Perceived Performance
SLIDE 36
SLIDE 37
SLIDE 38
SLIDE 39
853ms
SLIDE 40
3:00
SLIDE 41
PHP is 0.4% of the total
SLIDE 42
Assume it gets 50% faster
SLIDE 43
The net saving is 0.2%
SLIDE 44
Not Noticeable
SLIDE 45
Frontend Performance should be your Priority
SLIDE 46
YSlow
SLIDE 47
SLIDE 48
SLIDE 49
SLIDE 50
SLIDE 51
Fix all of these
SLIDE 52
I’ve done all that!
SLIDE 53
Optimisation Procedure
SLIDE 54 Optimisation Procedure
- 1. Figure out what’s slow
SLIDE 55 Optimisation Procedure
- 1. Figure out what’s slow
- 2. Figure out why it’s slow
SLIDE 56 Optimisation Procedure
- 1. Figure out what’s slow
- 2. Figure out why it’s slow
- 3. Make it faster
SLIDE 58
Choose your target
SLIDE 59 Choose your target
SLIDE 60 Choose your target
- Slower than the rest
- Heavily used
SLIDE 61
SLIDE 62
SLIDE 63
SLIDE 64
SLIDE 65
SLIDE 66
SLIDE 67
SLIDE 68
Grep your Logs
SLIDE 69
Grep your Logs
Completed in 220ms (View: 72, DB: 5)
SLIDE 70
Grep your Logs
Completed in 220ms (View: 72, DB: 5) X-Runtime: 0.00783
SLIDE 71
PL Analyze
SLIDE 73
Caching!
SLIDE 74
HTTP Caching
SLIDE 75
ETags
SLIDE 76
ETags
ETag: b4c2fedde6926a5ee6ed8cd5cc995592
SLIDE 77
ETags
ETag: b4c2fedde6926a5ee6ed8cd5cc995592 If-None-Match: b4c2fedde6926a5ee6ed8cd5cc995592
SLIDE 78 ETags
ETag: b4c2fedde6926a5ee6ed8cd5cc995592 If-None-Match: b4c2fedde6926a5ee6ed8cd5cc995592
def
handle_etag
etag!
[@user.id,
@user.updated_at]
end
SLIDE 79
Last-Modified
SLIDE 80
Last-Modified
Last-Modified: Sun, 28 Sep 2008 19:38:05 GMT
SLIDE 81
Last-Modified
Last-Modified: Sun, 28 Sep 2008 19:38:05 GMT If-Not-Modified-Since: Sun, 28 Sep 2008 19:38:05 GMT
SLIDE 82 Last-Modified
Last-Modified: Sun, 28 Sep 2008 19:38:05 GMT If-Not-Modified-Since: Sun, 28 Sep 2008 19:38:05 GMT
def
handle_last_modified
last_modified!
@user.updated_at
end
SLIDE 83 Expires
def
add_expires
response.headers["Expires"]
=
@user.updated_at
+
1.hour
end
SLIDE 84
Rails Caching
SLIDE 85 Fragment Caching
<%
cache
[@user,
"dashboard"]
do
%>
<%=
render
:partial=>"complex_expensive_dashboard"
%> <%
end
%>
SLIDE 86 Action Caching
caches_action
:index
SLIDE 87 Model Caching
class
Comment
<
ActiveRecord::Base
acts_as_cached
:ttl=>1.hour end
SLIDE 88 Memcache
- Seriously Fast
- Seriously Scalable
- LRU Cache Expunging
SLIDE 89
Cache Generations
There are only two hard things in Computer Science: cache invalidation and naming things
SLIDE 90
Cache Generations
There are only two hard things in Computer Science: cache invalidation and naming things So let’s not explicitly invalidate anything
SLIDE 91 Cache Generations
cache
["user",
@user.id]
cache
["user_profile",
@user.id]
SLIDE 92 Cache Generations
cache
["user",
@user.id,
@user.generation]
cache
["user_profile",
@user.id,
@user.generation]
SLIDE 93 Cache Generations
class
User
<
ActiveRecord::Base
before_save
:increment_generation
def
increment_generation
self.generation
+=
1
end end
SLIDE 94 Cache Generations
class
Friendship
<
ActiveRecord::Base
belongs_to
:owner,
:class_name=>"User"
belongs_to
:friend,
:class_name=>"User"
before_save
:increment_both_users
def
increment_both_users
[owner,
friend].map
&:increment_generation
end end
SLIDE 96
Never Guess
SLIDE 97
You’re never right
SLIDE 98
Slow Trac
SLIDE 99 Slow Trac
- Use a new PostgreSQL driver
SLIDE 100 Slow Trac
- Use a new PostgreSQL driver
- Use mod_python, not tracd
SLIDE 101 Slow Trac
- Use a new PostgreSQL driver
- Use mod_python, not tracd
- It’s spammers, add a better spam plugin
SLIDE 102 Slow Trac
@project.head_revision
SLIDE 103 Slow Trac
SELECT
max(rev)
FROM
rev;
SLIDE 104
Slow Trac
SELECT
rev
FROM
rev
ORDER
BY
‐LENGTH(rev),
rev
LIMIT
1;
SLIDE 105 Slow Trac
SELECT
max(rev::integer)
FROM
rev;
SLIDE 106 Slow Trac
CREATE
INDEX
rev_as_INT
ON
rev(rev::integer);
SLIDE 107
Performance Tests
New in 2.2
SLIDE 108 Performance Tests
require
'performance/test_helper' class
SessionActionsTest
<
ActionController::PerformanceTest
SLIDE 109 Performance Test
def
test_fetching_index
get
"/sessions"
end
def
test_fetching_index_all
get
"/sessions?all=true"
end
SLIDE 110
Performance Test
rake test:benchmark
SLIDE 111 Performance Test
rake test:benchmark
SessionActionsTest#test_fetching_index
(54
ms
warmup)
process_time:
70
ms
memory:
0.00
KB
objects:
0
gc_runs:
0
gc_time:
0
ms
SLIDE 112 Performance Test
def
setup
u
=
users(:koz)
post
authenticate_url,
...
50.times
do
|i|
u.sessions.create!
end
end
SLIDE 113 Performance Tests
SessionActionsTest#test_fetching_index
(254
ms
warmup)
process_time:
270
ms
memory:
0.00
KB
objects:
0
gc_runs:
0
gc_time:
0
ms
SLIDE 114
Performance Tests
rake test:profile
SLIDE 115 Performance Tests
SessionActionsTest#test_fetching_index_process_time_graph.html
SLIDE 116
Performance Tests
SLIDE 117
Performance Tests
SLIDE 118
Performance Tests
SLIDE 119 Performance Tests
SessionActionsTest#test_fetching_index_all_objects_graph.html
SLIDE 120
Performance Tests
SLIDE 121
Performance Tests
SLIDE 122
Performance Tests
SLIDE 123
Performance Tests
SLIDE 125 <%=
image_tag
"red.png"
%>
<img
src="/images/red.png?1212215830"
/>
SLIDE 126 <%=
image_tag
"onepixel.gif"
%>
SLIDE 127 Route Generation
<p>Have
you
considered
<%=
link_to
"Paying
me
money",
awesome_consultant_url
%></p>
SLIDE 128 Route Generation
<p>Have
you
considered
<%=
link_to
"Paying
me
money",
awesome_consultant_url
%></p>
SLIDE 129 N+1 Queries
<%
@user.posts.each
do
|post|
%>
<p><%=
link_to
post.title,
post_url(post)
%></p> <%
end
%>
SLIDE 130 N+1 Queries
User.find(params[:id],
:include=>[:posts])
SLIDE 131 N+1 Queries
<%
@user.posts.each
do
|post|
%>
<p>
<%=
link_to
post.title,
post_url(post)
%>
by
<%=
post.author.name
%>
</p> <%
end
%>
SLIDE 132 Garbage Collection
PageShowTest#test_show
(246
ms
warmup)
process_time:
183
ms
memory:
1696.26
KB
objects:
116280
gc_runs:
0
gc_time:
42
ms
22% in GC
SLIDE 133
Garbage Collection
http://github.com/skaes/railsbench
SLIDE 134 Garbage Collection
#
greatly
increase
initial
heap
slots
available
(60x
more)
RUBY_HEAP_MIN_SLOTS=600000
#
ensure
lots
of
heap
slots
are
freed
after
GC
(25x
more)
RUBY_HEAP_FREE_MIN=100000
#
and
don’t
run
GC
unless
we're
over
60MB
heap
(7.5x
more)
RUBY_GC_MALLOC_LIMIT=60000000
SLIDE 135
Conclusion
SLIDE 136
Fix Frontend Performance First
SLIDE 137
Use HTTP
SLIDE 138
Cache with memcache
SLIDE 139
Profile, don’t guess
SLIDE 140
Don’t forget GC when profiling
SLIDE 141 Questions?
Michael Koziarski michael@koziarski.com