rupicon 2014 caching

58
1 / 58

Upload: rupicon

Post on 30-Jun-2015

156 views

Category:

Technology


0 download

DESCRIPTION

Rupicon 2014 caching

TRANSCRIPT

Page 1: Rupicon 2014 caching

1 / 58

Page 2: Rupicon 2014 caching

Rails CachingAlexandru Keszeg

(Software Developer @ PITECH+PLUS)

2 / 58

Page 3: Rupicon 2014 caching

This is Rails, how hard can it be?

3 / 58

Page 4: Rupicon 2014 caching

This is Rails, how hard can it be?<% cache @user do %> <%= render partial: 'user' %><% end %>

4 / 58

Page 5: Rupicon 2014 caching

Thank you for your time

5 / 58

Page 6: Rupicon 2014 caching

TOPICS

6 / 58

Page 7: Rupicon 2014 caching

TOPICS

WHY?

fragment caching

SQL caching

HTTP Caching

expiration

7 / 58

Page 8: Rupicon 2014 caching

WHY?

8 / 58

Page 9: Rupicon 2014 caching

WHY?

9 / 58

Page 10: Rupicon 2014 caching

Fragment Cachingcache small pieces of information instead of full pages

10 / 58

Page 11: Rupicon 2014 caching

1. Do not expire keys

2. Model associations govern expiry

3. Nested caching is best

11 / 58

Page 12: Rupicon 2014 caching

Do not expire keys

12 / 58

Page 13: Rupicon 2014 caching

cache_key

user = User.first

=> User id: 1, updated_at: "2014-10-02 18:12:57"

user.cache_key

=> users/1-20141002181257446781000

13 / 58

Page 14: Rupicon 2014 caching

cache_key

user = User.first

=> User id: 1 , updated_at: "2014-10-02 18:12:57"

user.cache_key

=> users/1-20141002181257446781000

14 / 58

Page 15: Rupicon 2014 caching

<% cache @user do %> <%= render partial: 'user' %><% end %>

15 / 58

Page 16: Rupicon 2014 caching

<% cache @user do %> <%= render partial: 'user' %><% end %>

First time we load the pageRead fragment views/users/1-20141002181257446781000/ 95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms)Write fragment views/users/1-20141002181257446781000/ 95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms)

All other page loads:Read fragment views/users/1-20141002181257446781000/ 95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms)

16 / 58

Page 17: Rupicon 2014 caching

Model associations govern expiry

17 / 58

Page 18: Rupicon 2014 caching

class Project has_many :todo_lists

class TodoList belongs_to :project, touch: true has_many :todos

class Todo belongs_to :todo_list, touch: true

when something changes, its direct parent is also changed

18 / 58

Page 19: Rupicon 2014 caching

Nested caching is best

19 / 58

Page 20: Rupicon 2014 caching

- @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

when something changes, we don't have to regenerate the entirepage

20 / 58

Page 21: Rupicon 2014 caching

Before Rails4

- @projects.each do |project| - cache ['v1', 'project', project] do

21 / 58

Page 22: Rupicon 2014 caching

Rails 4 solves this problem with cache digests.

A call to #cache in your views will now suffix a digest of the template and itsdependencies. No longer will you need to worry about fragment cachedependencies and versioning!

user.cache_key=> users/1-20141002181257446781000

Read fragment views/users/1-20141002181257446781000/ 95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms)

22 / 58

Page 23: Rupicon 2014 caching

Rails 4 solves this problem with cache digests.

A call to #cache in your views will now suffix a digest of the template and itsdependencies. No longer will you need to worry about fragment cachedependencies and versioning!

user.cache_key=> users/1-20141002181257446781000

Read fragment views/users/1-20141002181257446781000/ 95a652b13f0fc5b7bc8785ac31b6b8b9 (1.2ms)

23 / 58

Page 24: Rupicon 2014 caching

Rails 4 solves this problem with cache digests.

A call to #cache in your views will now suffix a digest of the template and itsdependencies. No longer will you need to worry about fragment cachedependencies 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

Page 25: Rupicon 2014 caching

Exercise

25 / 58

Page 26: Rupicon 2014 caching

26 / 58

Page 27: Rupicon 2014 caching

27 / 58

Page 28: Rupicon 2014 caching

Perspective data

Or how to make your cache useless

28 / 58

Page 29: Rupicon 2014 caching

Consider the impact of a single button

29 / 58

Page 30: Rupicon 2014 caching

.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

Page 31: Rupicon 2014 caching

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

Page 32: Rupicon 2014 caching

Solutions

32 / 58

Page 33: Rupicon 2014 caching

Solutions

Move it client side

.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

Page 34: Rupicon 2014 caching

There's gem for that!

34 / 58

Page 35: Rupicon 2014 caching

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

Page 36: Rupicon 2014 caching

36 / 58

Page 37: Rupicon 2014 caching

benefits

cache more stuff

more hits

faster pages

less stuff in cache

more hits

less RAM used

37 / 58

Page 38: Rupicon 2014 caching

The fastest code is no code

38 / 58

Page 39: Rupicon 2014 caching

The fastest code is no code

Leverage the browser cache

39 / 58

Page 40: Rupicon 2014 caching

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 endend

40 / 58

Page 41: Rupicon 2014 caching

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 endend

41 / 58

Page 42: Rupicon 2014 caching

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 endend

=> "304 Not Modified"

42 / 58

Page 43: Rupicon 2014 caching

Edge cachingCloudfront

Akamai

fastly

Varnish...

43 / 58

Page 44: Rupicon 2014 caching

Follow these rules

everything common is served in the initial request

perspective data done with js in a different http request

expiration is handled by HTTP headers

44 / 58

Page 45: Rupicon 2014 caching

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 nseconds.

Cache-Control: s-maxage=[seconds] - Same as max-age but applies specificallyto proxy

45 / 58

Page 46: Rupicon 2014 caching

What about my models?

46 / 58

Page 47: Rupicon 2014 caching

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 endend

*all is for rails3

47 / 58

Page 48: Rupicon 2014 caching

How do i expire that?

48 / 58

Page 49: Rupicon 2014 caching

How do i expire that?

49 / 58

Page 50: Rupicon 2014 caching

There's a gem for thatahawkins/cashier

50 / 58

Page 51: Rupicon 2014 caching

Tag based cachingInstead of using updated_at to cause a cache miss, record where your objectsare 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']).allend

...

User#after_commit...

Cashier.expire "users"

51 / 58

Page 52: Rupicon 2014 caching

But i want hourly updates on my homepage,oh please will you help me?

52 / 58

Page 53: Rupicon 2014 caching

But i want hourly updates on my homepage,oh please will you help me?

cache 'homepage', expires_in: 1.hour do

53 / 58

Page 54: Rupicon 2014 caching

This thing doesn't update, i want a buttonto nuke everything!

class NukesController

def create expire_fragment('homepage'); end

end

54 / 58

Page 55: Rupicon 2014 caching

How well is it working for us?

55 / 58

Page 56: Rupicon 2014 caching

Where to go from here?

Caching with Rails: An overview

IdentityCache

Multi-fetch Fragments

SecondLevelCache

56 / 58

Page 57: Rupicon 2014 caching

Thank you

57 / 58

Page 58: Rupicon 2014 caching

58 / 58