GithubHelp home page GithubHelp logo

about_caching's Introduction

About Caching

Testing on the Rails 4.2.3 and Ruby 2.2.1, using Dalli gem and memcached for cache store.

Fragment Caching

Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in.

From the Rails guild for caching, for example, If we wanna show all the events placed on the website in real time and didn't want to cache that part of the page, but did want to cache the part of the page which lists all events available, we could do this:

<% cache cache_key_for_events do %>
    <% @events.each do |event| %>
      <tr>
            <td><%= event.title %></td>
            <td><%= event.start_date %></td>

def cache_key_for_events
  count = Event.count
  max_updated_at = Event.maximum(:updated_at).try(:utc).try(:to_s, :number)
  "events/all-#{count}-#{max_updated_at}"
end

It does 2 simple DB call Event.count and Event.maximum(:updated_at) to avoid doing a much more expensive call Event.all:

Started GET "/events" for ::1 at 2015-10-06 11:52:32 +1100 Processing by EventsController#index as HTML (0.2ms) SELECT COUNT(*) FROM "events" (0.1ms) SELECT MAX("events"."updated_at") FROM "events" Cache digest for app/views/events/index.html.erb: ca56ea987eb8ca17dd4852b8a2060d3d Cache read: views/events/all-3-20151005093155/ca56ea987eb8ca17dd4852b8a2060d3d Read fragment views/events/all-3-20151005093155/ca56ea987eb8ca17dd4852b8a2060d3d (4.2ms) We can also nests the multiple fragments, if just a single event is updated, other events would still not be effected and we can still pull them from the cache. <% cache event do %> <%= event.title %>

With eager load

But what if we have to do eager loader, we have to do this expensive call because the first cache read for the collection missed. In Rails, this is typically done using includes. If a Event has many Attendees:

<% cache @cache_key_for_events do %>
  <% @events.each do |event| %>
    <tr>
      <% cache event do %>
        <td><%= event.title %></td>
        <td><%= event.start_date %></td>
        <td><%= event.end_date %></td>
        <td><%= event.location %></td>
        <td>
            <% event.users.each do |user| %>
            <%= user.name %>,
            <% end %>
        </td>

The first time, it will use the inner join to do the expensive query:

Started GET "/events" for ::1 at 2015-10-06 18:13:51 +1100
ActiveRecord::SchemaMigration Load (0.6ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by EventsController#index as HTML
   (0.6ms)  SELECT COUNT(*) FROM "events"
   (0.2ms)  SELECT MAX("events"."updated_at") FROM "events"
Cache digest for app/views/events/index.html.erb: a6fc9d29103a0b152fe44d7a7fe3a68e
Cache read: views/events/all-8-20151006042329/a6fc9d29103a0b152fe44d7a7fe3a68e
Dalli::Server#connect 127.0.0.1:11211
Read fragment views/events/all-8-20151006042329/a6fc9d29103a0b152fe44d7a7fe3a68e (3.3ms)
  SQL (0.7ms)  SELECT "events"."id" AS t0_r0, "events"."title" AS t0_r1, "events"."start_date" AS t0_r2, "events"."end_date" AS t0_r3, "events"."location" AS t0_r4, "events"."agenda" AS t0_r5, "events"."address" AS t0_r6, "events"."created_at" AS t0_r7, "events"."updated_at" AS t0_r8, "users"."id" AS t1_r0, "users"."name" AS t1_r1, "users"."event_id" AS t1_r2, "users"."created_at" AS t1_r3, "users"."updated_at" AS t1_r4 FROM "events" INNER JOIN "users" ON "users"."event_id" = "events"."id"
  Cache digest for app/views/events/index.html.erb: a6fc9d29103a0b152fe44d7a7fe3a68e
Cache read: views/events/1-20151006033600344917000/a6fc9d29103a0b152fe44d7a7fe3a68e
Read fragment views/events/1-20151006033600344917000/a6fc9d29103a0b152fe44d7a7fe3a68e (1.2ms)
Cache write: views/events/1-20151006033600344917000/a6fc9d29103a0b152fe44d7a7fe3a68e

But then it will check the cache first, if there's no update, it will just get the fragment from the cache without query:

Started GET "/events" for ::1 at 2015-10-06 18:14:12 +1100
Processing by EventsController#index as HTML

(0.2ms) SELECT COUNT(*) FROM "events" (0.2ms) SELECT MAX("events"."updated_at") FROM "events" Cache digest for app/views/events/index.html.erb: a6fc9d29103a0b152fe44d7a7fe3a68e Cache read: views/events/all-8-20151006042329/a6fc9d29103a0b152fe44d7a7fe3a68e Read fragment views/events/all-8-20151006042329/a6fc9d29103a0b152fe44d7a7fe3a68e (2.4ms) Rendered events/index.html.erb within layouts/application (7.3ms) Completed 200 OK in 207ms (Views: 205.3ms | ActiveRecord: 0.4ms) We can also use the touch: method if we want the parent element expire when its associated child is updated. With touch set to true, in this case, any action which changes updated_at for a attendee/user record will also change it for the associated event, thereby expiring the cache. From the bottom to the top, the single event that has been touched and also cache for all events will be updated:

Cache read: views/events/1-20151006074846596169000/a6fc9d29103a0b152fe44d7a7fe3a68e
Read fragment views/events/1-20151006074846596169000/a6fc9d29103a0b152fe44d7a7fe3a68e (1.5ms)
Cache write: views/events/1-20151006074846596169000/a6fc9d29103a0b152fe44d7a7fe3a68e
Write fragment views/events/1-20151006074846596169000/a6fc9d29103a0b152fe44d7a7fe3a68e (1.6ms)
  Cache digest for app/views/events/index.html.erb: a6fc9d29103a0b152fe44d7a7fe3a68e

Cache read: views/events/2-20151005092449106634000/a6fc9d29103a0b152fe44d7a7fe3a68e
Read fragment views/events/2-20151005092449106634000/a6fc9d29103a0b152fe44d7a7fe3a68e (1.7ms)
  Cache digest for app/views/events/index.html.erb: a6fc9d29103a0b152fe44d7a7fe3a68e
Cache read: views/events/3-20151005093155609052000/a6fc9d29103a0b152fe44d7a7fe3a68e
Read fragment views/events/3-20151005093155609052000/a6fc9d29103a0b152fe44d7a7fe3a68e (1.6ms)

Cache write: views/events/all-8-20151006074846/a6fc9d29103a0b152fe44d7a7fe3a68e
Write fragment views/events/all-8-20151006074846/a6fc9d29103a0b152fe44d7a7fe3a68e (2.1ms)

Conditional get:

We can also use the 304(Not Modified) response to let browsers to pull from the client cache:

It's still based on the updated_at and we can just use the fresh_when helper to render:

  def show
    fresh_when last_modified: @event.updated_at, etag: @event
  end

Started GET "/events/3" for ::1 at 2015-10-06 14:34:24 +1100
Processing by EventsController#show as HTML
  Parameters: {"id"=>"3"}
  Event Load (0.1ms)  SELECT  "events".* FROM "events" WHERE "events"."id" = ? LIMIT 1  [["id", 3]]
  Cache digest for app/views/events/show.html.erb: 62ec1adf0505f487dd47308f8da14b41
Completed 304 Not Modified in 5ms (ActiveRecord: 0.1ms)

But there's a problem: if user logs in after that and goes back to click the show, it will also trigger 304 and the page will be the same without changing user's status. But we can add the current_user.id into the etag:

fresh_when last_modified: @event.updated_at, etag: [@event, current_user.try(:id)]

Or cleaner way:

class EventsController < ApplicationController
	etag { current_user.try(:id) }
    ...
    def show
	  fresh_when(@event)
    end

about_caching's People

Contributors

judywu29 avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.