1 / 58
1?GJQ?AFGLE 
 JCV?LBPS*CQXCE 
	2MDRU?PC#CTCJMNCP/(3$'
/+42
 
2 / 58
3FGQGQ1?GJQ
FMUF?PBA?LGR@C 
3 / 58
3FGQGQ1?GJQ
FMUF?PBA?LGR@C 
% cache @user do % 
%= render partial: 'user' % 
% end % 
4 / 58
3F?LIWMSDMPWMSPRGKC 
5 / 58
3./(2 
6 / 58
3./(2 
6'8 
DP?EKCLRA?AFGLE 
20+A?AFGLE 
'33/?AFGLE 
CVNGP?RGML 
7 / 58
6'8 
8 / 58
6'8 
9 / 58
%P?EKCLR?AFGLE 
cache small pieces of information instead of full pages 
10 / 58
#MLMRCVNGPCICWQ 
,MBCJ?QQMAG?RGMLQEMTCPLCVNGPW 
-CQRCBA?AFGLEGQ@CQR 
11 / 58
#MLMRCVNGPCICWQ 
12 / 58
A?AFC=ICW 
SQCP4QCPDGPQR 
= User id: 1, updated_at: 2014-10-02 18:12:57 
SQCPA?AFC=ICW 
= users/1-20141002181257446781000 
13 / 58
A?AFC=ICW 
SQCP4QCPDGPQR 
= User id: 1 , updated_at: 2014-10-02 18:12:57 
SQCPA?AFC=ICW 
= users/1-20141002181257446781000 
14 / 58
% cache @user do % 
%= render partial: 'user' % 
% end % 
15 / 58
% cache @user do % 
%= render partial: 'user' % 
% end % 
%GPQRRGKCUCJM?BRFCN?EC 
Read fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
Write fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
 JJMRFCPN?ECJM?BQ 
Read fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
16 / 58
,MBCJ?QQMAG?RGMLQEMTCPLCVNGPW 
17 / 58
class Project 
has_many :todo_lists 
class TodoList 
belongs_to :project, touch: true 
has_many :todos 
class Todo 
belongs_to :todo_list, touch: true 
UFCLQMKCRFGLEAF?LECQ
GRQBGPCARN?PCLRGQ?JQMAF?LECB 
18 / 58
-CQRCBA?AFGLEGQ@CQR 
19 / 58
- @projects.each do |project| 
- cache ['project', project] do 
...project 
- project.todo_lists.each do |todo_list| 
- cache ['todo_list', todo_list] do 
...todo list 
- todo_list.todos.each do |todo_list| 
- cache ['todo', todo] do 
...todo 
UFCLQMKCRFGLEAF?LECQ
UCBMLRF?TCRMPCECLCP?RCRFCCLRGPC 
N?EC 
20 / 58
!CDMPC1?GJQ 
- @projects.each do |project| 
- cache ['v1', 'project', project] do 
21 / 58
1?GJQQMJTCQRFGQNPM@JCKUGRFA?AFCBGECQRQ 
A call to #cache in your views will now suffix a digest of the template and its 
dependencies. No longer will you need to worry about fragment cache 
dependencies and versioning! 
user.cache_key 
= users/1-20141002181257446781000 
Read fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
22 / 58
1?GJQQMJTCQRFGQNPM@JCKUGRFA?AFCBGECQRQ 
A call to #cache in your views will now suffix a digest of the template and its 
dependencies. No longer will you need to worry about fragment cache 
dependencies and versioning! 
user.cache_key 
= users/1-20141002181257446781000 
Read fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
23 / 58
1?GJQQMJTCQRFGQNPM@JCKUGRFA?AFCBGECQRQ 
A call to #cache in your views will now suffix a digest of the template and its 
dependencies. No longer will you need to worry about fragment cache 
dependencies and versioning! 
user.cache_key 
= users/1-20141002181257446781000 
Read fragment views/users/1-20141002181257446781000/ 
95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms) 
There's a gem for that 
rails/cache_digests 
24 / 58
$VCPAGQC 
25 / 58
26 / 58
27 / 58
/CPQNCARGTCB?R? 
Or how to make your cache useless 
28 / 58
MLQGBCPRFCGKN?ARMD?QGLEJC@SRRML 
29 / 58
.col-sm-12.no-lr-padding.ticket-info 
.col-price-block 
%span.top-line 
From 
%span.ticket-price.bottom-line 
= formated_cheapest_ticket_price(activity) 
.col-tickets-left-block 
- unless activity.hide_remaining_tickets? 
%span.color-default.top-line 
Tickets left: 
%span.bottom-line 
= activity_tickets_left(activity) 
.col-attendees-block 
%span.top-line 
Attendees: 
%span.bottom-line 
= activity_attendees_count(activity) 
.user-buttons-block 
%a.btn.btn-wishlist.btn-85 
%span.event_wishlist_span 
= wishlisted_label(activity, current_user) 
30 / 58
hit rate? 
cache ['event', event] do 
turns into: 
cache ['event', event, current_user] do 
Hit rate: 1 / Event.count * User.count 
*probably should avoid 
31 / 58
2MJSRGMLQ 
32 / 58
2MJSRGMLQ 
,MTCGRAJGCLRQGBC 
.event-pod{data-wishlisted=#{ 
wishlisted_label(activity, current_user)}} 
$('.event-pod').each(function(){ 
$(this).find('.wishlisted_button').html( 
$(this.data('wishlisted')) 
); 
}) 
??? 
This is good if you want to decouple the usere data or generate it in 
Also good for edge caching. 
*extra complexity 
33 / 58
3FCPCQECKDMPRF?R 
34 / 58
neighborland/cache_rocket 
= render_cached 'outer', replace: 'inner' 
outer.html.haml 
- cache 'outer' do 
.lots 
.of 
.htmls 
= cache_replace_key 'inner' 
inner.html.haml 
= uncacheable_content 
35 / 58
36 / 58
@CLCDGRQ 
A?AFCKMPCQRSDD 
more hits 
faster pages 
JCQQQRSDDGLA?AFC 
more hits 
less RAM used 
37 / 58
3FCD?QRCQRAMBCGQLMAMBC 
38 / 58
3FCD?QRCQRAMBCGQLMAMBC 
+CTCP?ECRFC@PMUQCPA?AFC 
39 / 58
def show 
@article = Article.find(params[:id]) 
if stale?(:etag = @article, :last_modified 
= @article.created_at.utc) 
@statistics = @article.really_expensive_call 
respond_to do |format| 
# all the supported formats 
end 
end 
end 
40 / 58
def show 
@article = Article.find(params[:id]) 
if stale?(:etag = @article, :last_modified 
= @article.created_at.utc) 
@statistics = @article.really_expensive_call 
respond_to do |format| 
# all the supported formats 
end 
end 
end 
41 / 58
def show 
@article = Article.find(params[:id]) 
if stale?(:etag = @article, :last_modified 
= @article.created_at.utc) 
@statistics = @article.really_expensive_call 
respond_to do |format| 
# all the supported formats 
end 
end 
end 
= 304 Not Modified 
42 / 58
$BECA?AFGLE 
Cloudfront 
Akamai 
fastly 
Varnish... 
43 / 58
%MJJMURFCQCPSJCQ 
CTCPWRFGLEAMKKMLGQQCPTCBGLRFCGLGRG?JPCOSCQR 
NCPQNCARGTCB?R?BMLCUGRFHQGL?BGDDCPCLRFRRNPCOSCQR 
CVNGP?RGMLGQF?LBJCB@W'33/FC?BCPQ 
44 / 58
Cache-Control: public - Any cache can store a copy of the content. 
Cache-Control: private - Don't store, this is for a single user. 
Cache-Control: no-cache - Re-validate before serving this content. 
Cache-Control: no-store - Don't store this content. Ever. At all. Please. 
Cache-Control: public, max-age=[seconds] - Caches can store this content for n 
seconds. 
Cache-Control: s-maxage=[seconds] - Same as max-age but applies specifically 
to proxy 
45 / 58
6F?R?@MSRKWKMBCJQ 
46 / 58
class SuggestionsController 
def show 
@users = Rails.cache.fetch(['users', params['page']]) do 
# we need to call .all to hit the database otherwise 
# we cache only the ruby query object 
User.suggestions.page(params['page']).all 
end 
end 
end 
*all is for rails3 
47 / 58
'MUBMGCVNGPCRF?R 
48 / 58
'MUBMGCVNGPCRF?R 
49 / 58
3FCPCQ?ECKDMPRF?R 
ahawkins/cashier 
50 / 58
3?E@?QCBA?AFGLE 
Instead of using updated_at to cause a cache miss, record where your objects 
are cached 
cache @something, tag: ['dashboard', 'settings'] 
@users = Rails.cache.fetch(['users', params['page']], 
tag: 'users') do 
# we need to call .all to hit the database otherwise 
# we cache only the ruby query object 
User.suggestions.page(params['page']).all 
end 
... 
User#after_commit 
... 
Cashier.expire users 
51 / 58
!SRGU?LRFMSPJWSNB?RCQMLKWFMKCN?EC
 
MFNJC?QCUGJJWMSFCJNKC 
52 / 58
!SRGU?LRFMSPJWSNB?RCQMLKWFMKCN?EC
 
MFNJC?QCUGJJWMSFCJNKC 
cache 'homepage', expires_in: 1.hour do 
53 / 58
3FGQRFGLEBMCQLRSNB?RC
GU?LR?@SRRML 
RMLSICCTCPWRFGLE 
class NukesController 
def create 
expire_fragment('homepage'); 
end 
end 
54 / 58
'MUUCJJGQGRUMPIGLEDMPSQ 
55 / 58
6FCPCRMEMDPMKFCPC 
?AFGLEUGRF1?GJQ LMTCPTGCU 
(BCLRGRW?AFC 
,SJRGDCRAF%P?EKCLRQ 
2CAMLB+CTCJ?AFC 
56 / 58
3F?LIWMS 
57 / 58

Rupicon 2014 caching